Java 锁原理,java偏向锁和轻量级锁
00-1010 1.偏置锁2的核心原理。偏置锁定代码3演示。偏置锁1的扩展和撤销。撤销偏置锁2。批量重偏和撤销3。偏置锁扩展综述
00-1010如果一个没有线程竞争的线程获得了一个锁,那么这个锁就进入了偏置状态。此时,标记字的结构变为偏置锁结构,锁对象的锁标志位(lock)变为01,偏置标志位(biased_lock)变为1。然后线程的ID被记录在锁对象的标志字中(由CAS操作完成)。以后线程获取锁时,可以通过判断线程ID和标志位直接进入同步块,甚至不需要CAS操作,省去了很多与锁申请相关的操作,从而提高了程序的性能。
关键点:无竞争
缺点:如果锁对象经常被多个线程争用,那么偏向锁是多余的,其撤销的过程会带来一定的性能开销。
00-1010偏置锁定默认延迟,程序启动时不会立即生效。如果想避免延迟,可以添加VM参数。
-xx 3360 biasedlockingstartupdelay=0以禁用延迟。
包内锁;导入org . open JDK . jol . info . class layout;导入org . open JDK . jol . VM . VM;公共类InnerLockTest { int a=1;双b=1.1public static void main(String[]args){ system . out . println(VM . current()。详细信息());Person person=新人();class layout layout=class layout . parse instance(person);new thread(()-{ system . out . println( before get bias lock:);system . out . println(layout . top printable());synchronized(person){ system . out . println( get bias lock:);system . out . println(layout . top printable());}System.out.println(获取偏置锁后:);system . out . println(layout . top printable());}、 thread1 )。start();} }班级成员{}
禁用偏向锁:添加 VM 参数 -XX:-UseBiasedLocking
00-1010如果有多个线程竞争有偏向的锁,这个对象锁已经有偏向了,其他线程发现有偏向的锁没有偏向自己,说明有竞争。尝试撤销偏锁(大概是引入安全点),然后扩展到轻量级锁。
00-1010 1.在安全点停止拥有锁的线程
2.遍历线程的堆栈帧,检查是否有锁记录。如果有锁记录,需要清除锁记录,使其解锁,修复锁记录指向的标志字,并清除其线程ID。
3.将当前锁升级为轻型锁
4.唤醒当前线程
撤销偏向锁的条件(满足其一即可):
1.多个线程竞争有偏向的锁。
2.调用有偏锁对象的HashCode()方法或System.identityHashCode()方法计算对象的HashCode后,将hash代码放入Mark Word中,内置锁将变为解锁状态,有偏锁将被撤销。
目录
批量重偏向解决的问题:
一个线程创建大量对象并执行初始同步操作,然后在另一个线程中使用这些对象作为后续操作的锁。在这种情况下,会导致大量有偏差的锁撤销操作。
包内锁;导入Java . util . ArrayList;导入org . open JDK . jol . info . class layout;导入组织操作
enjdk.jol.vm.VM;public class InnerLockTest {int a=1;double b=1.1;public static void main(String[] args) throws InterruptedException {System.out.println(VM.current().details());ArrayList<Person> list=new ArrayList<Person>();new Thread(()->{for(int i=0;i<100;i++){Person person=new Person();synchronized (person) {list.add(person);}}try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}},"thread1").start();Thread.sleep(3000);new Thread(()->{for(int i=0;i<30;i++){Person person=list.get(i);synchronized (person) {if(i==17i==18i==19i==21){System.out.println("第"+(i+1)+"次偏向结果:");System.out.println(ClassLayout.parseInstance(person).toPrintable());}}}},"thread2").start();}}class Person{}
结果分析:
先用线程1创建了100个对象锁,这些对象锁都偏向于线程1,后面创建线程2去争夺这些锁,前19次线程2都是抢占失败获得轻量级锁(失败过程中阈值增加),第20次抢占时达到阈值20,这时JVM会认为自己是不是不应该偏向线程1,于是之后开始偏向线程2,线程2之后获得的都是偏向锁
第1-19个对象由于线程2在抢占过程中变为轻量级锁,锁释放后变为无锁状态第20-30个对象触发批量重定向,锁释放后依旧偏向线程2第31-100个对象依然和开始一样偏向线程1,锁释放后依旧偏向线程1批量撤销解决的问题:
存在明显多线程竞争的场景下使用偏向锁是不合适的,例如生产者/消费者队列
package innerlock;import java.util.ArrayList;import org.openjdk.jol.info.ClassLayout;import org.openjdk.jol.vm.VM;public class InnerLockTest {int a=1;double b=1.1;public static void main(String[] args) throws InterruptedException {System.out.println(VM.current().details());ArrayList<Person> list=new ArrayList<Person>();new Thread(()->{for(int i=0;i<100;i++){Person person=new Person();synchronized (person) {list.add(person);}}try {//为了防止JVM线程复用,在创建完对象后,保持线程t1状态为存活Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}},"thread1").start();Thread.sleep(3000);new Thread(()->{for(int i=0;i<40;i++){Person person=list.get(i);synchronized (person) {if(i==18i==19i==39i==41){System.out.println("t2 第"+(i+1)+"次偏向结果:");System.out.println(ClassLayout.parseInstance(person).toPrintable());}}}try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}},"thread2").start();Thread.sleep(3000);new Thread(()->{for(int i=20;i<40;i++){Person person=list.get(i);synchronized (person) {if(i==20i==39){System.out.println("t3 第"+(i+1)+"次偏向结果:");System.out.println(ClassLayout.parseInstance(person).toPrintable());}}}},"thread3").start();Thread.sleep(1000);System.out.println("新创建对象:"+ClassLayout.parseInstance(new Person()).toPrintable());}}class Person{}
做法:
以class为单位,为每个class维护一个偏向锁撤销计数器,每一次该class的对象发生偏向撤销操作时,该计数器+1,当这个值达到重偏向阈值默认20时,JVM就认为该class的偏向锁有问题,因此会进行批量重偏向。每个class对象会有一个对应的epoch字段,每个处于偏向锁状态对象的mark word中也有该字段,其初始值为创建该对象时,class中的epoch的值。每次发生批量重偏向时,就将该值+1,同时遍历JVM中所有线程的栈,找到该class所有正处于加锁状态的偏向锁,将其epoch字段改为新值。下次获得锁时,发现当前对象的epoch值和class的epoch不相等,那就算当前已经偏向了其他线程,也不会执行撤销操作,而是直接通过CAS操作将其mark word的Thread Id 改成当前线程Id
当达到重偏向阈值后,假设该class计数器继续增长,当其达到批量撤销的阈值后(默认40),JVM就认为该class的使用场景存在多线程竞争,会标记该class为不可偏向,之后,对于该class的锁,直接走轻量级锁的逻辑
小结:
3. 偏向锁的膨胀
如果偏向锁被占据,一旦有第二个线程争抢这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到内置锁偏向状态,这时表明在这个对象锁上已经存在竞争了。JVM检查原来持有该对象锁的占有线程是否依然存活,如果挂了,就可以将对象变为无锁状态,然后进行重新偏向,偏向抢锁线程。如果JVM检查到原来的线程依然存活,就进一步检查占有线程的调用堆栈是否通过锁记录持有偏向锁。如果存在锁记录,就表明原来的线程还在使用偏向锁,发生锁竞争,撤销原来的偏向锁,将偏向锁膨胀(INFLATING)为轻量级锁
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注盛行IT的更多内容!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。