java Synchronized的优化

Synchronized实现:

  1. 如果synchronized关键字修饰代码块,编译后的代码以monitorenter和monitorexit包围起来
  2. 如果synchronized修饰的方法,方法的flag中会有ACC_SYNCHRONIZED标记.方法执行时,如果flag中有ACC_SYNCHRONIZED时,会去争取monitor 每一个对象都有一个监视器锁(monitor),当monitor被占用时标识对象已经被锁定了

java对象头

java对象在内存中的存储分为以下几部分

  1. 对象头

1-1 对象头

  1. 实例数据
  2. 对其填充

重量级锁(多个线程共同竞争同一个临界区)

Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因

临界区: 需要获取到对象监视器锁(monitor)才能执行的部分代码。

轻量级锁 (多个线程交替的进入临界区)

本意为线程交替执行临界区代码,并不存在竞争的情况,如果同一时间多个线程竞争访问同一个锁,轻量级锁会膨胀成重量级锁
由对象头的情况来看,当轻量级锁的情况下,对象头内存放的指向站内锁记录的指针

什么是锁记录:
1.轻量级锁,会在当前的线程栈中,分配一块名字叫Lock Record(锁记录)的空间,用于拷贝锁对象的mark word
2.当轻量级锁释放的时候,锁记录中的mark word会重新写会锁对象中。

轻量级锁的加锁过程

  1. 在代码进入同步块的时候,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word。这时候线程堆栈与对象头的状态如图2.1所示。
  2. 拷贝对象头中的Mark Word复制到锁记录中。
  3. 拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock record里的owner指针指向object mark word。如果更新成功,则执行步骤4,否则执行步骤5。
  4. 如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如图2.2所示。
  5. 如果这个更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。 而当前线程便尝试使用自旋来获取锁,自旋就是为了不让线程阻塞,而采用循环去获取锁的过程。

加锁成功后:
1.对象头mark word 标记为00
2.锁记录是原先对象头中mark word的拷贝,并且锁记录中的owner执行对象头的mark word
3.对象头中原先mark word的内容是指向锁记录的指针

轻量级锁的解锁

  1. 通过CAS操作尝试把线程中复制的Displaced Mark Word对象替换当前的Mark Word。
  2. 如果替换成功,整个同步过程就完成了。
  3. 如果替换失败,说明有其他线程尝试过获取该锁(此时锁已膨胀),那就要在释放锁的同时,唤醒被挂起的线程。

偏向锁(一个线程使用一把锁的情况)

如果只有一个线程竞争锁的情况下,jvm只需要使用偏向锁就可以了。因为偏向锁更加的高效,由上面的介绍可以看到,加偏向锁时,会涉及到mark word内容的拷贝,以及多个cas操作。偏向锁更加的方便,只需要做一次cas 轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能。

偏向锁的加锁过程

  1. 访问Mark Word中偏向锁的标识是否设置成1,锁标志位是否为01——确认为可偏向状态(无锁和偏向锁时都是01)。
  2. 如果为可偏向状态,则测试线程ID是否指向当前线程,如果是,进入步骤(5),否则进入步骤(3)。
  3. 如果线程ID并未指向当前线程,则通过CAS操作竞争锁。如果竞争成功,则将Mark Word中线程ID设置为当前线程ID,然后执行(5);如果竞争失败,执行(4)。
  4. 如果CAS获取偏向锁失败,则表示有竞争。当到达全局安全点(safepoint)时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码。
  5. 执行同步代码

偏向锁的释放

偏向锁不会主动释放,偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态,撤销偏向锁后恢复到未锁定(标志位为“01”)或轻量级锁(标志位为“00”)的状态

归纳(Synchronized的优化方式):

上面介绍了重量级锁、轻量级锁、偏向锁,其实轻量级锁和偏向锁都是jvm对Synchronized的一个优化(当竞争还不是很紧张的时候,采用轻巧一点的办法)

其他的优化方式

当然jvm还有别的优化,比如:

轻量级锁膨胀成重量级锁时候的自旋

因为获取重量级锁时候,线程上下文切换会是效率低下的原因之一。所以,在当前线程去竞争重量级锁时,使用自旋的方式,不放弃cpu资源,如果能在自旋期间获取到了锁也是减少了线程上下文的切换。

由于获取到锁的时间不确定性,所以jvm对锁自旋的时间采用了自适应的方式。即:如果上次在自旋期间获取到锁了,那么这次自旋的时间的适当加长,如果上次没有在自旋期间加到锁,那么这次自旋的时间适当减短。

锁粗化

将多个连续在一次的加锁解锁操作合并成一个大的锁操作

锁消除

锁消除即删除不必要的加锁操作。根据代码逃逸技术,如果判断到一段代码中,堆上的数据不会逃逸出当前线程,那么可以认为这段代码是线程安全的,不必要加锁

参考资料:

www.cnblogs.com/paddix/p/54… www.zhihu.com/question/53…

猜你喜欢

转载自juejin.im/post/5c9e3602f265da307b2d3c38