Synchronized

Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。

作用:

  1. 原子性:确保线程互斥的访问同步代码;
  2. 可见性:保证共享变量的修改能够及时可见,其实是通过Java内存模型中的 “对一个变量unlock操作之前,必须要同步到主内存中;如果对一个变量进行lock操作,则将会清空工作内存中此变量的值,在执行引擎使用此变量前,需要重新从主内存中load操作或assign操作初始化变量值” 来保证的;
  3. 有序性:有效解决重排序问题,即 “一个unlock操作先行发生(happen-before)于后面对同一个锁的lock操作”;

用法:

  1. 当synchronized作用在实例方法时,监视器锁(monitor)便是对象实例(this);
  2. 当synchronized作用在静态方法时,监视器锁(monitor)便是对象的Class实例,因为Class数据存在于永久代,因此静态方法锁相当于该类的一个全局锁;
  3. 当synchronized作用在某一个对象实例时,监视器锁(monitor)便是括号括起来的对象实例;

同步原理

在JVM中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充。Synchronized用的锁就是存在Java对象头里。

 

  1. 实例数据:存放类的属性数据信息,包括父类的属性信息;
  2. 对齐填充:由于虚拟机要求 对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐;
  3. 对象头: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。

  1. 检测Mark Word是否为可偏向状态,即是否为偏向锁1,锁标识位为01;
  2. 若为可偏向状态,则测试线程ID是否为当前线程ID,如果是,则执行步骤(5),否则执行步骤(3);
  3. 如果测试线程ID不为当前线程ID,则通过CAS操作竞争锁,竞争成功,则将Mark Word的线程ID替换为当前线程ID,否则执行线程(4);
  4. 通过CAS竞争锁失败,证明当前存在多线程竞争情况,当到达全局安全点,获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码块;
  5. 执行同步操作;

3.轻量级锁

当存在超过一个线程通过CAS获取同一个锁时,会发生偏向锁的撤销,偏向锁撤销后会对象可能处于两种状态:锁标志位为00。

  一种是不可偏向的无锁状态,已经获得偏向锁的线程已经退出了同步操作,这时候会撤销偏向锁,升级成轻量级锁。

  一种是不可偏向的已锁状态,已经获得偏向锁的线程正在进行同步操作,这时候会升级成轻量级锁并被原持有锁的线程获得锁。

  1. 在线程进入同步块时,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝。

  2. 拷贝对象头中的Mark Word复制到锁记录(Lock Record)中。

  3. 拷贝成功后,虚拟机将使用CAS操作尝试将对象Mark Word中的Lock Word更新为指向当前线程Lock Record的指针,并将Lock record里的owner指针指向object mark word。如果更新成功,则执行步骤(4),否则执行步骤(5)。

  4. 如果这个更新动作成功了,那么当前线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态。

  5. 如果这个更新操作失败了,虚拟机首先会检查对象Mark Word中的Lock Word是否指向当前线程的栈帧,如果是,就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,进入自旋执行(3),若自旋结束时仍未获得锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,当前线程以及后面等待锁的线程也要进入阻塞状态。

 

4.重量级锁

当多个线程通过CAS自旋 修改 Mark Word来获取同一个锁时将会引起锁膨胀,就会升级为重量级锁,锁标志位为10。

 

优缺点:

 

posted @ 2020-10-13 11:53  柒月丶  阅读(170)  评论(0)    收藏  举报