有关synchronized锁的知识点,我用一篇文章总结了

简介: 在多线程的程序执行中,有可能会出现多个线程会同时访问一个共享并且可变资源的情况,这种时候由于线程的执行是不可控的,所以必须采用一些方式来控制该资源的访问,这种方式就是“加锁”。我们把那些可能会被多个线程同时操作的资源称为临界资源,加锁的目的就是让这些临界资源在同一时刻只能有一个线程可以访问。

听说微信搜索《Java鱼仔》会变更强哦!


本文收录于JavaStarter,里面有我完整的Java系列文章,学习或面试都可以看看哦


(一)概述


在多线程的程序执行中,有可能会出现多个线程会同时访问一个共享并且可变资源的情况,这种时候由于线程的执行是不可控的,所以必须采用一些方式来控制该资源的访问,这种方式就是“加锁”。


我们把那些可能会被多个线程同时操作的资源称为临界资源,加锁的目的就是让这些临界资源在同一时刻只能有一个线程可以访问。


(二)CAS的介绍


CAS:compare and swap,比较且交换。使用CAS操作可以在没有锁的情况下完成多线程对一个值的更新。CAS的具体操作如下:


当要更新一个值时,先获取当前值E,计算更新后的结果值V(先不更新),当要去更新这个值时,比较此时这个值是否还是等于E,如果相等,则将E更新为V,如果不相等,则重新进行上面的操作


以i++操作为例,在没有锁的情况下,这个操作是线程不安全的,假设i的初始值为0,CAS操作先获取原值E=0,计算更新后的值V=1,要更新之前先比较这个值是否还是等于0,如果等于0则将E更新为1,如果不等于0则说明有线程已经更新了,重新获取E值=1,继续执行。


ABA问题


CAS操作可能会出现ABA问题,ABA问题即我们要去比较的这个值E,经过多个线程的操作后从0变成1又变成了0。此时虽然E值和更新前相等,但是还是已经被更新了。


ABA问题的解决办法


对E值增加一个版本号,每次要获取数据时将版本号也获取,每次更新完数据之后将版本号递增,这样就算值相等通过版本号也能知道是否经过修改。


java在很多地方都用到了CAS操作,比如Atomic的一些类:


AtomicIntegeri=newAtomicInteger();


进入AtomicInteger方法中,可以看到有个叫Unsafe的类,进入这个类中,可以看到CAS的几个操作方法


网络异常,图片无法展示
|


(三)对象在内存中的存储布局


要想学会synchronized,首先要理解Java对象的内存布局,或者称为内存结构。


网络异常,图片无法展示
|


一个对象分为对象头、实例数据和对其填充。


其中对象头Header占12个字节:Mark Word占8个字节,类型指针class pointer占4个字节(默认经过了压缩,如果不开启压缩占8个字节)


实例对象按实际存储有不同大小,对象为空时等于0。


Padding表示对齐,当此时内存所占字节不能被8整除时补上相应字节数。


以Object o=new Object()为例,我们先导入一个jol依赖,通过jol可以看到具体的内存布局


<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.9</version></dependency>

运行以下代码:

publicstaticvoidmain(String[] args) {
Objecto=newObject();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}

观察结果,OFFSET表示偏移量的起始点,SIZE表示所占字节,前两行是Mark Word一共占8个字节,第三行是class pointer占4个字节,此时对象为空,实例对象等于0,最后padding补齐,一共16个字节。


网络异常,图片无法展示
|


(三)synchronized


synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,synchronized把锁信息存放在对象头的MarkWord中。


synchronized作用在非静态方法上是对方法的加锁,synchronized作用在静态方法上是对当前的类加锁。


在早期的jdk版本中,synchronized是一个重量级锁,保证线程的安全但是效率很低。后来对synchronized进行了优化,有了一个锁升级的过程


无锁态(new)-->偏向锁-->轻量级锁(自旋锁)-->重量级锁


通过MarkWord中的8个字节也就是64位来记录锁信息。也有人将自旋锁称为无锁,因为自选操作并没有给一个对象上锁,这里只要理解意思即可。


网络异常,图片无法展示
|


3.1 锁升级过程详解:


当给一个对象增加synchronized锁之后,相当于上了一个偏向锁


当有一个线程去请求时,就把这个对象MarkWord的ID改为当前线程指针ID(JavaThread),只允许这一个线程去请求对象。


当有其他线程也去请求时,就把锁升级为轻量级锁。每个线程在自己的线程栈中生成LockRecord,用CAS自旋操作将请求对象MarkWordID改为自己的LockRecord,成功的线程请求到了该对象,未成功的对象继续自旋。


如果竞争加剧,当有线程自旋超过一定次数时(在JDK1.6之后,这个自旋次数由JVM自己控制),就将轻量级锁升级为重量级锁,线程挂起,进入等待队列,等待操作系统的调度。


3.2 加锁的字节码实现


synchronized关键字被编译成字节码之后会被翻译成monitorenter和monitorexit两条指令,进入同步代码块时执行monitorenter,同步代码块执行完毕后执行monitorexit


(四)锁消除


在某些情况下,如果JVM认为不需要锁,会自动消除锁,比如下面这段代码:


publicvoidadd(Stringa,Stringb){
StringBuffersb=newStringBuffer();
sb.append(a).append(b);
}

StringBuffer是线程安全的,但是在这个add方法中stringbuffer是不能共享的资源,因此加锁只会徒增性能消耗,JVM就会消除StringBuffer内部的锁。


(五)锁粗化


在某些情况下,JVM检测到一连串的操作都在对同一个对象不断加锁,就会将这个锁加到这一连串操作的外部,比如:


StringBuffersb=newStringBuffer();
while(i<100){
sb.append(str);
i++;
}

上述操作StringBuffer每次添加数据都要加锁和解锁,连续100次,这时候JVM就会将锁加到更外层(while)部分。


(六)逃逸分析


首先问一个经常基础的虚拟机问题,实例对象存放在虚拟机的哪个位置?按以前的回答,示例对象放在堆上,引用放在栈上,示例的元数据等存放在方法区或者元空间。

但这是有前提的,前提是示例对象没有线程逃逸行为。


JDK1.7开始默认开启了逃逸分析,所谓逃逸分析,就是指如果一个对象被编译器发现只能被一个线程访问,那么这个对象就不需要考虑同步。JVM就对这种对象进行优化,将堆分配转化为栈分配,归根结底就是虚拟机在编译过程中对程序的一种优化行为。



开启逃逸分析:­XX:+DoEscapeAnalysis关闭逃逸分析:­XX:­-DoEscapeAnalysis
相关文章
|
2月前
|
安全 Java
Synchronized是怎么实现的?
Synchronized是怎么实现的?
|
4月前
|
Java
【多线程面试题十三】、说一说synchronized与Lock的区别
这篇文章讨论了Java中`synchronized`和`Lock`接口在多线程编程中的区别,包括它们在实现、使用、锁的释放、超时设置、锁状态查询以及锁的属性等方面的不同点。
|
4月前
|
存储 安全 Java
【多线程面试题十七】、如果不使用synchronized和Lock,如何保证线程安全?
这篇文章探讨了在不使用`synchronized`和`Lock`的情况下保证线程安全的方法,包括使用`volatile`关键字、原子变量、线程本地存储(`ThreadLocal`)以及设计不可变对象。
|
6月前
|
安全 Java 开发者
一文弄懂synchronized
一文弄懂synchronized
82 0
|
存储 Java
第二季:5公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自旋锁【Java面试题】
第二季:5公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自旋锁【Java面试题】
65 0
|
7月前
|
Java
9.synchronized 是个啥东西?应该怎么使用?
9.synchronized 是个啥东西?应该怎么使用?
60 0
9.synchronized 是个啥东西?应该怎么使用?
阅读完synchronized和ReentrantLock的源码后,我竟发现其完全相似
阅读完synchronized和ReentrantLock的源码后,我竟发现其完全相似
|
7月前
|
Java
Java多线程—同步代码块问题解决方法
Java多线程—同步代码块问题解决方法
51 0
|
设计模式 安全 Java
JUC第十二讲:JUC锁 - 看不懂锁核心类 AQS 原理来打我
JUC第十二讲:JUC锁 - 看不懂锁核心类 AQS 原理来打我
|
存储 Java
【一文读懂】 Java并发 - 锁升级原理
Java对象头,锁升级的原因,重量级锁、轻量级锁、偏向锁的原理
405 0
【一文读懂】 Java并发 - 锁升级原理