java 偏向锁

文章目录

  • 偏向锁
    • 1、偏向锁是什么
    • 2、优缺点
      • 优点
      • 缺点
    • 3、偏向锁怎么获取
      • 前提
    • 4、偏向锁的撤销
    • 5、可重偏向状态(Rebiasable)
    • 6、BiasedLockingBulkRebiasThreshold 参数是干什么用的
    • 7、到BiasedLockingBulkRevokeThreshold 参数是干什么用的
    • 8、执行下方demo需要准备的环境
    • 9、总结和猜想
    • 10、参考资料
    • LAST、 demo代码

偏向锁

1、偏向锁是什么

大多数情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。JDK1.6之后 偏向锁默认开启
偏向锁是锁状态中最乐观的一种锁:从始至终只有一个线程请求同一把锁

偏向锁对象头的Mark Word:

线程ID Epoch 对象分代年龄 偏向锁的标志 是否偏向锁 0否 1是 锁标志位
线程ID(初始为00…代表未偏向) Epoch 对象分代年龄 1 01

2、优缺点

优点

只需要执行一次CAS即可获取锁
采用延迟释放锁策略
锁重入时,只需要判断mark_word.threadId(关于对象头的文章)是否为当前threadId即可

缺点

总体上只针对第一个线程有效,新线程获取锁时,会导致锁膨胀
锁膨胀时,会导致stop the world (STW)
与原生hashcode()互斥,导致偏向锁并非适应于所有的instance

3、偏向锁怎么获取

前提

JVM偏向锁(-XX:+UseBiasedLocking)默认已开启
确认instance可用偏向锁可用,即mark word 偏向锁的标志为 101

当一个线程访问同步块并获取锁时,

会在对象头和栈帧中的锁记录里存储锁偏向的线程ID

,以后该线程在进入和退出同步块时不需要花费CAS操作来加锁和解锁,

而只需简单的测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁,

如果测试成功,

表示线程已经获得了锁,

如果测试失败,

则需要再测试下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁)

,如果没有设置,

则使用CAS竞争锁,竞争成果后为轻量级锁

如果设置了,

则尝试使用CAS将对象头的偏向锁指向当前线程。

引用网络上对于获取偏向锁的总结,解释了批量重偏向和批量撤销偏向的原理

获取偏向锁的步骤

1、验证对象的bias位

如果是0,则该对象不可偏向,应该使用轻量级锁算法。

2、验证对象所属InstanceKlass的prototype的bias位

确认prototype的bias为是否被设置。如果没有设置,则该类所有对象全部不允许被偏向锁定;并且该类所有对象的bias位都需要被重置,使用轻量级锁替换。

3、校验epoch位

校验对象的MarkWord的epoch位是否与该对象所属InstanceKlass的prototype的MarkWord的epoch匹配。如果不匹配,则表明偏向已过期,需要重新偏向。这种情况,偏向线程可以简单地使用原子CAS指令重新偏向于这个锁对象。

4、校验owner线程

比较偏向线程ID与当前线程ID。如果匹配,则表明当前线程已经获得了偏向,可以安全返回。如果不匹配,对象锁被假定为匿名偏向状态,当前线程应该尝试使用CAS指令获得偏向。如果失败的话,就尝试撤销(很可能引入安全点),然后回退到轻量级锁;如果成功,当前线程成功获得偏向,可直接返回。

4、偏向锁的撤销

偏向锁使用了一种等到竞争出现才释放锁的机制,

所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。

偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),

它会首先暂停拥有偏向锁的线程,

然后检查持有偏向锁的线程是否活着,

如果线程不处于活动状态,则将对象头设置成无锁状态,

如果线程仍然活着,恢复到无锁(标记对象不适合作为偏向锁),最后唤醒暂停的线程。这里路神说的是不会有101变为001但感觉这里修改了偏向锁标识然后竞争轻量级锁。因为无法证明得啃jvm源码了,求同存异

需要拿锁的线程再去加锁,只能使用轻量级锁了(批量重偏向除外)

5、可重偏向状态(Rebiasable)

在此状态下,偏向锁的epoch字段是无效的(与锁对象对应的klass的mark_prototype的epoch值不匹配)。下一个试图获取锁对象的线程将会面临这个情况,使用原子CAS指令可将该锁对象绑定于当前线程。在批量重偏向的操作中,未被持有的锁对象都被至于这个状态,以便允许被快速重偏向。本段摘抄自网络,关于重偏向是否CAS稍后讨论

6、BiasedLockingBulkRebiasThreshold 参数是干什么用的

BiasedLockingBulkRebiasThreshold是针对某一Class的实例对象在某一线程偏向锁重偏向的阈值默认是20,
需要注意。如果没达到这个值发生偏向,请check线程id,两个线程是否一样。
当当前线程撤销偏向的次数==20以后线程内该类的实例会处于以下状态 总次数应该在InstanceKlass上,没有翻jvm源码,有待验证

  1. 已经经过偏向锁撤销,并使用轻量级锁的对象,状态为001 无锁状态demo1
  2. 当前需要加锁的对象,会check epoch 和偏向锁的标志 重偏向至当前线程 关于重偏向是否CAS逐渐明朗
  3. 后续未加锁未使用的对象 依然是偏向线程1 demo1
  4. 已经使用了轻量级锁的对象不可再次使用偏向锁 demo1
  5. 只要偏向锁在某线程内撤销次数达到此阈值,就修改在class元数据——InstanceKlass中的epoch 是对象和元数据的epoch不一致,而使对象可以重偏向。demo2
  6. 发生过重偏向的对象不可再次重偏向 自己写demo,这里不提供demo了。需要修改到BiasedLockingBulkRevokeThreshold=41 排除此参数的影响

7、到BiasedLockingBulkRevokeThreshold 参数是干什么用的

BiasedLockingBulkRevokeThreshold 是批量撤销对象的可偏向状态的判断阈值,由上面引用网络上对于获取偏向锁的总结
对后面demo2的现象做出了解释未验证,需要求教子路大仙

  1. 偏向锁撤销达到BiasedLockingBulkRevokeThreshold 的阈值(默认40)后,修改对象所属InstanceKlass的prototype的bias(偏向锁的标志是否可偏向)位 使该对象以及该类的所有实例,不再使用偏向锁。所以在默认条件下一个类的对象实例,只可以重偏向一次注意和上面的对象区分开。可修改此阈值。调整重偏向次数,或者提前批量撤销偏向
  2. 偏向锁撤销达到此次数之后,不会影响现有未加锁对象的头(未同步过和已同步过),但这些对象不可使用偏向锁
  3. 偏向锁撤销达到此次数之后由于类模板InstanceKlass的bias(偏向锁的标志是否可偏向)被设置为0,所以new出来的新对象,是无锁状态,不可使用偏向锁。

8、执行下方demo需要准备的环境

  1. 一个maven项目
  2. 引入依赖
<dependency>
            <groupId>org.openjdk.jolgroupId>
            <artifactId>jol-cliartifactId>
            <version>0.9version>
        dependency>
  1. 需要修改jvm项目的运行参数
 -XX:+PrintFlagsFinal 打印jvm运行时参数,验证参数修改
 -XX:BiasedLockingBulkRebiasThreshold=20 修改批量重偏向阈值
 -XX:BiasedLockingBulkRevokeThreshold=40 修改批量撤销偏向阈值。

9、总结和猜想

  1. 校验epoch位时候发现类模板和对象不一致的时候,可以简单的理解为此对象状态约等于 偏向锁未偏向的状态重新进行偏向加锁
  2. 当InstanceKlass类模板的偏向锁标识,标识不可偏向的时候。改类所有实例对象均不可进行偏向加锁
  3. 偏向锁重偏向一次之后不可再次重偏向。
  4. 已经使用过轻量级锁的对象,不可再次偏向,哪怕线程内触发了批量重偏向

10、参考资料

  1. 轻量级锁、偏向锁、重量级锁详情
  2. 是否真的理解了偏向锁、轻量级锁、重量级锁(锁膨胀)、自旋锁、锁消除、锁粗化,知道重偏向吗?
  3. 偏向锁,轻量级锁,自旋锁,重量级锁的详细介绍
  4. CAS操作、Java对象头、偏向锁的获取与撤销、轻量级锁的获取与撤销、锁粗化、锁消除
  5. Java锁事之偏向锁

LAST、 demo代码

//demo1

public class BiasedLockJOLTest {
     


    public static void main(String[] args) throws Exception {
     
        Thread.sleep(6000);
//        a = new A();
        List<A> list = new ArrayList<>();
        List<B> list2 = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
     
            list.add(new A());
            list2.add(new B());
        }

        Thread t1 = new Thread() {
     
            String name = "1";

            public void run() {
     
                out.printf(name);
                for (A a : list) {
     
                    synchronized (a) {
     
                        if (a == list.get(10))
                            out.println("t1 预期是偏向锁"+10 + ClassLayout.parseInstance(a).toPrintable());//偏向锁

                    }

                }
                try {
     
                    Thread.sleep(100000);
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }

            }
        };
        t1.start();
        Thread.sleep(5000);
        out.println("main 预期是偏向锁 同步结束后不撤销偏向锁,在下次使用的时候进行撤销偏向并膨胀为轻量级锁或者重锁 或重偏向"+10 + ClassLayout.parseInstance(list.get(10)).toPrintable());//偏向锁

        Thread t2 = new Thread() {
     
            String name = "2";

            public void run() {
     
                out.printf(name);

                for(int i = 0;i<100;i++){
     
                    A a = list.get(i);
                    if(i==20){
     
                        a= list.get(9);
                    }
                    synchronized (a) {
     
                        if ( a == list.get(10)) {
     
                            out.println("t2 i=10 get(1)预期是无锁" +  ClassLayout.parseInstance(list.get(1)).toPrintable());//偏向锁
                            out.println("t2 i=10 get(10) 预期轻量级锁 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向锁
                        }
                        if ( a == list.get(19)) {
     
                            out.println("t2  i=19  get(10)预期是无锁" + 10 + ClassLayout.parseInstance(list.get(10)).toPrintable());//无锁 不可偏向
                            out.println("t2  i=19  get(19) 满足重偏向条件20 预期偏向锁 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向锁

                            out.println("t2  i=19  get(40) 未同步到的对象,依然偏向t1 " + i + ClassLayout.parseInstance(list.get(40)).toPrintable());//偏向锁
                        }
                        if (i == 20) {
     
                            out.println("t2  i=20  get(9)预期是轻量级锁,因为无锁 不可偏向的标识不可重新更改为可偏向状态,所以再次锁定之前的对象依然使用了轻量级锁" + 10 + ClassLayout.parseInstance(a).toPrintable());//轻量级锁


                        }

                    }



                }



                try {
     
                    Thread.sleep(100000);
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }


            }
        };
        t2.start();
        }
    }
//demo2

public class BiasedLockJOLTest2 {
     
    static A a;

    public static void main(String[] args) throws Exception {
     
        Thread.sleep(6000);
//        a = new A();
        List<A> list = new ArrayList<>();
        List<A> list2 = new ArrayList<>();
        List<A> list3 = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
     
            list.add(new A());
            list2.add(new A());
            list3.add(new A());
        }
        out.println("初始状态" + 10 + ClassLayout.parseClass(A.class).toPrintable());//偏向锁

        Thread t1 = new Thread() {
     
            String name = "1";

            public void run() {
     
                out.printf(name);
                for (A a : list) {
     
                    synchronized (a) {
     
                        if (a == list.get(10))
                            out.println("t1 预期是偏向锁" + 10 + ClassLayout.parseInstance(a).toPrintable());//偏向锁
                    }
                }
                try {
     
                    Thread.sleep(100000);
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }

            }
        };
        t1.start();
        Thread.sleep(5000);
        out.println("main 预期是偏向锁" + 10 + ClassLayout.parseInstance(list.get(10)).toPrintable());//偏向锁

        Thread t2 = new Thread() {
     
            String name = "2";

            public void run() {
     
                out.printf(name);

                for (int i = 0; i < 100; i++) {
     
                    A a = list.get(i);
                    synchronized (a) {
     
                        if (a == list.get(10)) {
     
                            out.println("t2 i=10 get(1)预期是无锁" + ClassLayout.parseInstance(list.get(1)).toPrintable());//偏向锁
                            out.println("t2 i=10 get(10) 预期轻量级锁 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向锁
                        }
                        if (a == list.get(19)) {
     
                            out.println("t2  i=19  get(10)预期是无锁" + 10 + ClassLayout.parseInstance(list.get(10)).toPrintable());//偏向锁
                            out.println("t2  i=19  get(19) 满足重偏向条件20 预期偏向锁 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向锁
                            out.println("A类的对象累计撤销达到20");
                        }

                    }
                }

                try {
     
                    Thread.sleep(100000);
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }


            }
        };
        t2.start();

        Thread.sleep(5000);


        Thread t3 = new Thread() {
     
            String name = "3";

            public void run() {
     

                out.printf(name);
                for (A a : list2) {
     
                    synchronized (a) {
     
                        if (a == list2.get(10))
                            out.println("t3 预期是偏向锁" + 10 + ClassLayout.parseInstance(a).toPrintable());//偏向锁
                    }
                }
                try {
     
                    Thread.sleep(100000);
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }
            }
        };
        t3.start();
        Thread.sleep(5000);


        Thread t4 = new Thread() {
     
            String name = "4";

            public void run() {
     

                out.printf(name);

                for (int i = 0; i < 100; i++) {
     
                    A a = list2.get(i);
                    synchronized (a) {
     
                        if (a == list2.get(10)) {
     
                            out.println("t4 i=10 get(1)预期是无锁" + ClassLayout.parseInstance(list2.get(1)).toPrintable());//偏向锁
                            out.println("t4 i=10 get(10) 当前不满足重偏向条件 20 预期轻量级锁 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向锁
                        }
                        if (a == list2.get(19)) {
     
                            out.println("t4  i=19  get(10)预期是无锁" + 10 + ClassLayout.parseInstance(list2.get(10)).toPrintable());//偏向锁
                            out.println("t4 i=19 get(19) 当前满足重偏向条件 20 但A类的对象累计撤销达到40 预期轻量级锁 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向锁
                            out.println("A类的对象累计撤销达到40");
                        }
                        if (a == list2.get(20)) {
     
                            out.println("t4 i=20 get(20) 当前满足重偏向条件 20 预期轻量级锁 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向锁

                        }
                    }
                }

            }
        };
        t4.start();
        Thread.sleep(5000);


        out.println("main 预期是偏向锁" + 10 + ClassLayout.parseInstance(list3.get(0)).toPrintable());//偏向锁

        Thread t5 = new Thread() {
     
            String name = "5";

            public void run() {
     
                out.printf(name);
                for (A a : list3) {
     
                    synchronized (a) {
     
                        if (a == list3.get(10))
                            out.println("t5 预期是轻量级锁,A类的对象累计撤销达到40 不可以用偏向锁了" + 10 + ClassLayout.parseInstance(a).toPrintable());//偏向锁
                    }
                }
                try {
     
                    Thread.sleep(100000);
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }

            }
        };
        t5.start();
        Thread.sleep(5000);
        out.println("main 预期是偏向锁" + 10 + ClassLayout.parseInstance(list.get(10)).toPrintable());//偏向锁

        Thread t6 = new Thread() {
     
            String name = "6";

            public void run() {
     
                out.printf(name);

                for (int i = 0; i < 100; i++) {
     
                    A a = list3.get(i);
                    synchronized (a) {
     
                        if (a == list3.get(10)) {
     
                            out.println("t6 i=10 get(1)预期是无锁" + ClassLayout.parseInstance(list3.get(1)).toPrintable());//偏向锁
                            out.println("t6 i=10 get(10) 预期轻量级锁 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向锁
                        }
                        if (a == list3.get(19)) {
     
                            out.println("t6  i=19  get(10)预期是无锁" + 10 + ClassLayout.parseInstance(list3.get(10)).toPrintable());//偏向锁
                            out.println("t6  i=19  get(19) 满足重偏向条件20 但A类的对象累计撤销达到40 不可以用偏向锁了 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向锁

                        }

                    }
                }

                try {
     
                    Thread.sleep(100000);
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }


            }
        };
        t6.start();

        Thread.sleep(5000);


        out.println("由于A撤销锁次数达到默认的 BiasedLockingBulkRevokeThreshold=40 这里实例化的对象 是无锁状态" + ClassLayout.parseInstance(new A()).toPrintable());//偏向锁
        out.println("由于B撤销锁次数没达到默认的 BiasedLockingBulkRevokeThreshold=40 这里实例化的对象 是偏向锁可以偏向状态 理论上可以再次验证上面A类的相关操作" + ClassLayout.parseInstance(new B()).toPrintable());//偏向锁
        out.println("撤销偏向后状态" + 10 + ClassLayout.parseClass(A.class).toPrintable());//偏向锁

    }


}

你可能感兴趣的:(JAVA,synchronized)