欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java高效并发(十)----JVM中的锁优化策略

程序员文章站 2022-07-02 10:37:22
...

CAS操作 

比较交换算法,CAS操作包含三个参数CAS(V,E,N),V表示要更新的变量,E表示预期值,N表示新值,当且仅当现在V的值等于E才会把V的值更新为N,如果在V的值不等于E,说明有线程对V做了更改,则CAS更新失败,对于更新失败的线程可以继续尝试或者放弃更新。

简单的说就是你给出一个期望值,如果变量不是你想的期望值,那就说明别人操作过这个值了,你可以重新读取,更换期望值,然后再尝试更新就好了。

 对象头

要清楚除接下来讲的偏向锁和轻量级锁,首先要说明对象的对象头,我们知道对象存储在堆中,包含一个对象头部分,MarkWord(32位或者64位),对象头内容又分为两部分。一部分是存储指向方法区中对象类型数据的地址(判断对象是哪个类的实例),第二部分存储对象自身的运行时数据,比如hashcode,GC年龄,还有一个就是锁标志位。

  • 当这个对象处于未被锁定的状态时,锁标志位是01,然后有hashcode,GC年龄等信息。
  • 当对象处于偏向锁状态,锁标志位是01,只是增加一个指向这个拥有偏向锁的线程ID
  • 当对象处于轻量级锁状态,锁标志位是00,其他信息替换为指向拥有这个对象锁的线程的栈帧的指针
  • 当对象处于重量级锁状态,锁标志位是10,其他信息替换为指向拥有这个对象锁的线程的栈帧的指针
  • 当对象处于GC标记状态,锁标志位是11,其他信息为空,不需要记录信息

偏向锁

 偏向锁,顾名思义偏向第一个申请锁的线程。对于没有什么锁竞争的场景,偏向锁将会大大提高性能,因为大多数情况下,连续多次请求同一把锁是一个线程,适合单线程访问带同步的资源或方法

第一个线程获得对象的锁,JVM就会设置这个对象的对象头,锁标志位是01,然后利用CAS操作把指向这个拥有偏向锁的线程ID增加到MarkWord中,如果CAS更新成功,就进入到偏向模式。当这个线程再次请求锁时,只需要检查对象头中的线程ID还是不是自己,如果是继续执行,如果不是就检查对象头中的锁标识是否设置成01(表示当前是偏向锁):如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。这个时候如果另外一个线程也在申请锁JVM会根据这个对象目前的锁状态,如果是偏向锁状态,JVM会把持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,然后根据对象目前的锁状态,更新对象头锁标志,恢复被挂起的线程。偏向锁就失效了。

jvm中通过-XX:+UseBiasedLocking参数可以设置是否开启偏向锁

轻量级锁

 加锁:那么当一个线程尝试获得对象的锁时,JVM首先会把对象的对象头信息拷贝一份放入到自己的栈帧中,然后通过CAS操作让对象头的指针指向自己的栈帧,锁标志位设置为00,如果这个CAS操作成功,这个线程就获得对象的轻量级锁,如果CAS操作失败,JVM会查看对象的对象头的指针,如果指向当前线程,这个线程就开始执行,如果没有就说明别的线程已经抢占了这个对象的锁,当前线程便尝试使用自旋来获取锁,轻量级锁在多次CAS自旋的过程中没有获得锁,就会自动的升级成重量级锁

解锁:如果对象的对象头指针仍然指向现在线程,该线程使用CAS操作将自己栈帧中复制的markword拷贝回对象头,如果成功,解锁完成,如果失败,则说明有其他线程竞争锁,则释放锁的同时唤醒其他线程。

自旋锁

 JVM为了避免真实的将线程挂起,还会做最后的努力,就会使用自旋锁,当申请锁不得时,让线程原地做空循环,等待锁资源。

可以使用-XX:UseSpinning来开启自旋锁,在jdk1.6之后默认开启。当然自旋的次数要有限制,默认是10次,也可以使用参数-XX:PreBlockSpin来设置

在jdk1.6中引入了自适应应的自旋锁,JVM可以根据上一次自旋成功获得锁的时间以及锁的拥有者的状态来控制自旋时间。

锁消除

对于一些代码上要求同步,但是实际不存在共享资源竞争的锁,JVM会消除它,锁消除的判断依据是逃逸分析技术,也就一段代码中堆中的数据不会被其他线程访问到,就可以当做栈上的数据来对待,那么就是该线程私有的,肯定安全,无需加锁。

比如使用Vector容器时,下面代码v是局部变量,线程私有而且也没有发生逃逸,那么在每次add操作,JVM就会消除Vetcor本来操作的synchronized锁。但是如果下面代码返回的是v,那就发生逃逸,JVM就不会执行锁消除

public String[] createString(){
		Vector <String> v=new Vector();
		for(int i=0;i<200;i++){
			v.add(Integer.toString(i));
		}
		return v.toArray(new String[]{});
	}

逃逸分析是需要在-serve模式下进行的,可以使用参数-XX:+EliminateLock参数打开锁消除,-XX:+DoEscapeAnanlysis打开逃逸分析。