深入理解Java中的锁(一)
Java中锁的概念
自旋锁 : 是指当一个线程在获取锁的时候,如果锁已经被其他线程获取,那么该线程将循环等待,然后不断判断锁是否能够被成功获取,直到获取到锁才会退出循环。
乐观锁 : 假定没有冲突,在修改数据时如果发现数据和之前获取的不一致,则读最新数据,修改后重试修改
悲观锁 :假定会发生并发冲突,同步所有对数据的相关操作,从读数据就开始上锁
独享锁(写) : 给资源加上写锁,拥有该锁的线程可以修改资源,其他线程不能再加锁(单写)
共享锁(读) : 给资源加上读锁后只能读不能改,其他线程也只能加读锁,不能加写锁 (多读)
可重入锁 :线程拿到一把锁后,可以自由进入同一把锁所同步的代码
不可重入锁 :线程拿到一把锁后,不可以自由进入同一把锁所同步的代码
公平锁 :争抢锁的顺序,按照先来后到的顺序
非公平锁 :争抢锁的顺序,不按照先来后到的顺序
Java中几种重要的锁实现方式:synchronized, ReentrantLock, ReentrantReadWriteLock
同步关键字synchronized
- 用于实例方法,静态方法时,隐式指定锁对象
- 用于代码块时显示指定锁对象
- 锁的作用域:对象锁,类锁,分布式锁
synchronized特性:可重入,独享,悲观锁
锁优化:
- 锁消除是发生在编译器级别的一种锁优化方式,是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行削除(开启锁消除的参数:-xx:+DoEscapeAnalysis -XX:+EliminateLocks)
- 锁粗化是指有些情况下我们反而希望把很多次锁的请求合并成一个请求,以降低短时间内大量锁请求、同步、释放带来的性能损耗
Note: synchronized关键字,不仅实现同步,JMM中规定,synchronized要保证可见性(不能够被缓存)
synchronized用法代码示例:
public class Counter {
private static int i = 0;
// 等价于 synchronized(this)
public synchronized void update() {
i++;
}
public void updateBlock() {
synchronized (this) {
i++;
}
}
// 等价于 synchronized (Counter.class)
public static synchronized void staticUpdate() {
i++;
}
public static void staticUpdateBlock() {
synchronized (Counter.class) {
i++;
}
}
}
那么synchronized加锁在JVM中到底是如何实现的?
要了解synchronized加锁在JVM中是如何实现的,就有必要了解Java对象在JVM中到底是如何存储的。我们知道JVM中在方法区存储对象的引用,在堆中存储的对象实例。那么堆中存储的对象又有那些信息哪?其实堆中存储的对象主要由三部分组成,对象头,实例字段数据以及padding。对象头里面存储了指向方法区元数据的引用,实例字段数据就是存储了实际的字段数据,padding主要是为了补位,实例对象在堆中存储的时候必须是八字节的整数倍,不够的时候由padding占位补齐。
对象头中的数据有具体分为Mark World,Class Metadata Address以及Array Length
- Mark World : 一段32/64的内存区域,用来存储Hashcode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等
- Class Metadata Address : 指向类的元信息的引用
- Array Length : 如果是数组对象,会有一个Array Length用来标记数组的长度
轻量级锁
轻量级锁的加锁过程:
- 每个线程都会在栈帧中开辟一块内存空间叫 Lock Record
- 然后线程会把对象头中 Mark world 的内容拷贝到 Lock Record
- 然后,以拷贝的 Mark world 的 内存为旧值,以 Lock Record Address 为新值,通过CAS操作进行抢锁
- 如果Mark world通过CAS操作成功,则成功抢到锁
- 如果CAS操作失败会进行自旋一定的次数进行抢锁,如果一定次数还没抢到则升级为重量级锁
重量级锁
线程在获取轻量级锁失败的时候会进行自旋,如果不加以限制会对CPU资源造成较多的消耗,所以自旋一定的次数之后会升级成重量级锁。
我们知道Java中每个对象都会有一个对象监视器(Object Monitor, 即管程),而升级为重量级锁就需要用到这个Object Monitor。它会有一个owner用来标记这个锁被谁占用了,还有一个entry list用来存储未获得锁的线程,entry list中的线程都是blocked状态。假设两个线程T1,T2同时去获取重量级锁,如果T1获取到了锁,那么owner就会指向T1,而T2就会进入entry list进行等待,从而减少对CPU的消耗。
偏向锁
在JDK6以后,默认已经开启了偏向锁这个优化,可以通过JVM参数 -XX:-UseBiasedLocking来禁用偏向锁。若偏向锁开启,只有一个线程抢锁,可获取偏向锁。偏向锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要同步。大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。当锁对象第一次被线程获取的时候,线程使用CAS操作把这个线程的ID记录在对象Mark Word之中,同时置偏向标志位1。以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需要简单地测试一下对象头的Mark Word里是否存储着指向当前线程的ID。如果测试成功,表示线程已经获得了锁。当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束。根据锁对象目前是否处于被锁定的状态,撤销偏向后恢复到未锁定或轻量级锁定状态。
锁的升级过程

深入理解Java中的锁(一)的更多相关文章
- 深入理解Java中的锁
转载:https://www.jianshu.com/p/2eb5ad8da4dc Java中的锁 常见的锁有synchronized.volatile.偏向锁.轻量级锁.重量级锁 1.synchro ...
- 深入理解Java中的锁(二)
locks包结构层次 Lock 接口 方法签名 描述 void lock(); 获取锁(不死不休) boolean tryLock(); 获取锁(浅尝辄止) boolean tryLock(long ...
- 深入理解Java中的锁(三)
ReadWriteLock接口 读写锁维护一对关联锁,一个只用于读操作,一个只用于写操作.读锁可以由多个线程同时持有,又称共享锁.写锁同一时间只能由一个线程持有,又称互斥锁.同一时间,两把锁不能被不同 ...
- java 中的锁 -- 偏向锁、轻量级锁、自旋锁、重量级锁(转载)
之前做过一个测试,详情见这篇文章<多线程 +1操作的几种实现方式,及效率对比>,当时对这个测试结果很疑惑,反复执行过多次,发现结果是一样的: 1. 单线程下synchronized效率最高 ...
- java 中的锁 -- 偏向锁、轻量级锁、自旋锁、重量级锁
之前做过一个测试,详情见这篇文章<多线程 +1操作的几种实现方式,及效率对比>,当时对这个测试结果很疑惑,反复执行过多次,发现结果是一样的: 1. 单线程下synchronized效率最高 ...
- Java并发编程:Java中的锁和线程同步机制
锁的基础知识 锁的类型 锁从宏观上分类,只分为两种:悲观锁与乐观锁. 乐观锁 乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新 ...
- 深入理解Java中的不可变对象
深入理解Java中的不可变对象 不可变对象想必大部分朋友都不陌生,大家在平时写代码的过程中100%会使用到不可变对象,比如最常见的String对象.包装器对象等,那么到底为何Java语言要这么设计,真 ...
- 史上最全 Java 中各种锁的介绍
更多精彩原创内容请关注:JavaInterview,欢迎 star,支持鼓励以下作者,万分感谢. 锁的分类介绍 乐观锁与悲观锁 锁的一种宏观分类是乐观锁与悲观锁.乐观锁与悲观锁并不是特定的指哪个锁(J ...
- Java并发指南4:Java中的锁 Lock和synchronized
Java中的锁机制及Lock类 锁的释放-获取建立的happens before 关系 锁是java并发编程中最重要的同步机制.锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消 ...
随机推荐
- spring+rabbitmq+stomp搭建websocket消息推送(非spring boot方式)
前言: 两年前做过spring+activemq+stomp的ws推送,那个做起来很简单,但现在公司用的mq中间件是rabbitmq,因此需要通过rabbitmq去做ws通信.仔细搜了搜百度/谷歌,网 ...
- Android消息机制架构和源码解析
http://wangkuiwu.github.io/2014/08/26/MessageQueue/
- 「中高级前端必须了解的」JS中的内存管理
前言 像C语言这样的底层语言一般都有底层的内存管理接口,比如 malloc()和free()用于分配内存和释放内存. 而对于JavaScript来说,会在创建变量(对象,字符串等)时分配内存,并且在不 ...
- 如何用 Flutter 实现混合开发?闲鱼公开源代码实例
Flutter: 必火,转两篇软文预热哈哈~ 中文网: https://flutterchina.club/get-started/test-drive/ 如何用 Flutter 实现混合开发?闲鱼公 ...
- 长春理工大学第十四届程序设计竞赛(重现赛)B
B Bowling Game 题目链接:https://ac.nowcoder.com/acm/contest/912/B 题目 CUST的队员打完省赛后,小r带着大家去打保龄球. 保龄球是一项难度非 ...
- 2019.ccpc女生赛-wfinal总结
2019ccpc女生赛离它结束有四天了,在这个期间我想了很多,想了想还是决定写这个总结.作为这个队伍唯一的一名大一队员,我很庆幸,能跟着两个学姐一起打比赛,计爱玲师姐,即将工作,张莹俐学姐.这估计都是 ...
- Ruby字符串(2):String方法详细整理
String方法整理 官方手册 类方法 new new(str="") → new_str new(str="", encoding: enc) → new_s ...
- 【POJ - 2386】Lake Counting (dfs+染色)
-->Lake Counting 直接上中文了 Descriptions: 由于近日阴雨连天,约翰的农场中中积水汇聚成一个个不同的池塘,农场可以用 N x M (1 <= N <= ...
- CSDN,CNBLOGS博客文章一键转载插件(转载测试)
插件地址: https://greasyfork.org/zh-CN/scripts/381053-csdn%E5%8D%9A%E5%AE%A2%E6%96%87%E7%AB%A0%E8%BD%AC% ...
- Linux环境下虚拟环境virtualenv安装和使用(转)
virtualenv用于创建独立的Python环境,多个Python相互独立,互不影响,它能够: 1. 在没有权限的情况下安装新套件 2. 不同应用可以使用不同的套件版本 3. 套件升级不影响其他应用 ...