Synchronized
Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。
作用:
- 原子性:确保线程互斥的访问同步代码;
- 可见性:保证共享变量的修改能够及时可见,其实是通过Java内存模型中的 “对一个变量unlock操作之前,必须要同步到主内存中;如果对一个变量进行lock操作,则将会清空工作内存中此变量的值,在执行引擎使用此变量前,需要重新从主内存中load操作或assign操作初始化变量值” 来保证的;
- 有序性:有效解决重排序问题,即 “一个unlock操作先行发生(happen-before)于后面对同一个锁的lock操作”;
用法:
- 当synchronized作用在实例方法时,监视器锁(monitor)便是对象实例(this);
- 当synchronized作用在静态方法时,监视器锁(monitor)便是对象的Class实例,因为Class数据存在于永久代,因此静态方法锁相当于该类的一个全局锁;
- 当synchronized作用在某一个对象实例时,监视器锁(monitor)便是括号括起来的对象实例;
同步原理
在JVM中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充。Synchronized用的锁就是存在Java对象头里。

- 实例数据:存放类的属性数据信息,包括父类的属性信息;
- 对齐填充:由于虚拟机要求 对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐;
- 对象头:Java对象头一般占有2个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit,在64位虚拟机中,1个机器码是8个字节,也就是64bit),但是 如果对象是数组类型,则需要3个机器码,因为JVM虚拟机可以通过Java对象的元数据信息确定Java对象的大小,但是无法从数组的元数据来确认数组的大小,所以用一块来记录数组长度。)
Hotspot虚拟机的对象头主要包括两部分数据:Mark Word(标记字段)、Class Pointer(类型指针)。其中 Class Pointer是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,Mark Word用于存储对象自身的运行时数据,它是实现轻量级锁和偏向锁的关键。
| 锁状态 | 25 bit | 31 bit | 1 bit | 4 bit 分代年龄 | 1 bit 偏向锁 | 2 bit 锁标记位 | 
| 无锁 | unused | hashcode | unused | 0 | 01 | 
| 锁状态 | 54 bit | 2 bit | 1 bit | 4 bit 分代年龄 | 1 bit 偏向锁 | 2 bit 锁标记位 | 
| 偏向锁 | 当前线程指针 | Epoch | unused | 1 | 01 | 
| 锁状态 | 62 bit | 2 bit | ||||
| 轻量级锁 | 指向线程栈中 Lock Record 的指针 | 00 | ||||
| 重量级锁 | 指向互斥量(重量级锁)指针 | 10 | ||||
| GC标记信息 | CMS过程用到的标记信息 | 11 | ||||
分代年龄:
分代年龄占4位,即最大值为15,所以新生代中的对象在eden区和survior区进行15次转移,当达到最大值时,再转移到老年代
synchronized的优化
1 锁膨胀
上面讲到锁有四种状态,并且会因实际情况进行膨胀升级,其膨胀方向是:无锁——>偏向锁——>轻量级锁——>重量级锁,并且膨胀方向不可逆。
1.无锁
也就是代表对象的monitor对象并没有被线程所持有,代表的是对象处于无锁状态。偏向锁位为0,锁标志位为01。
2.偏向锁
偏向锁是在单线程执行代码块时使用的机制,即是否为偏向锁位为1,锁标识位为01。
- 检测Mark Word是否为可偏向状态,即是否为偏向锁1,锁标识位为01;
- 若为可偏向状态,则测试线程ID是否为当前线程ID,如果是,则执行步骤(5),否则执行步骤(3);
- 如果测试线程ID不为当前线程ID,则通过CAS操作竞争锁,竞争成功,则将Mark Word的线程ID替换为当前线程ID,否则执行线程(4);
- 通过CAS竞争锁失败,证明当前存在多线程竞争情况,当到达全局安全点,获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码块;
- 执行同步操作;
3.轻量级锁
当存在超过一个线程通过CAS获取同一个锁时,会发生偏向锁的撤销,偏向锁撤销后会对象可能处于两种状态:锁标志位为00。
一种是不可偏向的无锁状态,已经获得偏向锁的线程已经退出了同步操作,这时候会撤销偏向锁,升级成轻量级锁。
一种是不可偏向的已锁状态,已经获得偏向锁的线程正在进行同步操作,这时候会升级成轻量级锁并被原持有锁的线程获得锁。
- 
在线程进入同步块时,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝。 
- 
拷贝对象头中的Mark Word复制到锁记录(Lock Record)中。 
- 
拷贝成功后,虚拟机将使用CAS操作尝试将对象Mark Word中的Lock Word更新为指向当前线程Lock Record的指针,并将Lock record里的owner指针指向object mark word。如果更新成功,则执行步骤(4),否则执行步骤(5)。 
- 
如果这个更新动作成功了,那么当前线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态。 
- 
如果这个更新操作失败了,虚拟机首先会检查对象Mark Word中的Lock Word是否指向当前线程的栈帧,如果是,就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,进入自旋执行(3),若自旋结束时仍未获得锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,当前线程以及后面等待锁的线程也要进入阻塞状态。 
4.重量级锁
当多个线程通过CAS自旋 修改 Mark Word来获取同一个锁时将会引起锁膨胀,就会升级为重量级锁,锁标志位为10。
优缺点:

 
                    
                
 
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号