08给女朋友讲讲并发编程-轻量级锁、锁膨胀、自旋、锁消除、偏向锁

#一、轻量级锁 在多线程条件下,虽然一个对象会有多个线程访问,但是他们访问的时间是错开的(没有竞争关系),那么可以使用轻量级锁来优化。 ####1.使用轻量级锁的目的 降低无实际竞争关系的情况下,直接使用重量级锁带来的性能消耗。 ####2.轻量级锁的使用 轻量级锁对使用者是透明的,语法仍然是synchronized. 假设有两个方法同步块,对同一个对象加锁。 ``` static final Object object = new Object(); public static void method1(){ synchronized(obj){ //同步块A method2(); } } public static void method2(){ synchronized(obj){ //同步块B } } ``` - 创建锁记录对象(Lock Record),每个线程的栈帧都会包含锁记录结构,内部可以存储锁定对象的Mark Word 以及对象的指针。 ![轻量级锁-01](https://upload-images.jianshu.io/upload_images/24526179-94643dc2cef02c4d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - 让锁记录中的object reference指向锁对象,并尝试用cas替换object中的Mark Word,将Mark Word的值与Lock Record中的地址互换。 ![轻量级锁-02](https://upload-images.jianshu.io/upload_images/24526179-ec7be0707a245284.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - 如果cas成功,**那么对象头的Mark Word变成了lock record,锁标记从01(无锁状态)变成了00(轻量级锁)**,此时该线程加锁成功。 #二、锁膨胀 问:上述执行轻量级锁的过程中,如果cas失败了,怎么办? - 如果cas失败,说明存在竞争关系,需要升级为重量级锁,也称为锁膨胀。 **锁膨胀的过程** - 当Thread-0在执行同步代码块的过程中,Thread-1也来使用轻量级锁的方式去获取锁,发现对象头中的Mark Word锁标记已经从**01**变成了**00**,Thread-1加轻量级锁失败,进入锁膨胀流程。 ![锁膨胀过程01](https://upload-images.jianshu.io/upload_images/24526179-3d302182c676b365.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - 为object申请Monitor锁,让Object指向Monitor地址,锁标记从**00**变成**10**。 - 然后自己进入Monitor的阻塞队列entryList中,变成BLOCKED状态。 - 当Thread-0执行完同步代码块后,释放轻量级锁的时候发现Mark Word中的地址已经发生改变,解锁失败,将进入Monitor锁的解锁流程。 ![锁膨胀过程02](https://upload-images.jianshu.io/upload_images/24526179-8f5d9086a9ea4b56.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) #三、自旋 如果持有锁的线程很快就能将锁释放,那么其余线程就不需要进入EntryList阻塞队列中等待(内核态与用户态之间的切换进入阻塞状态)。它们只需要短时间的等待(自旋),等待持有锁的线程释放锁后,即可不用进入阻塞队列直接获取锁。 - 自旋也会消耗cpu的资源,如果自旋执行时间太长,会有大量的线程处于自旋状态而占用cpu资源,进而会影响整体的性能。那么如何去选择自旋的执行时间呢? JVM默认的限定次数是10次,超过线程也会进入EntryList阻塞队列中等待。 #四、锁消除 ``` static int x = 0; public void methodA(){ x++; }; public void methodB(){ Object obj = new Object(); synchronized(obj){ x++; } } ``` 上述代码中methodA()和methodB()方法不同点是methodB()方法对局部变量对象加锁去执行++操作,理论上synchronized会影响性能,降低代码的执行效率。但是通过Benchmark对两个方法进行测试,发现两个方法的执行时间几乎没有差别。 **为什么:** JVM中重要的核心模块之一 **JIT即时编译器**,它可以对字节码文件进一步优化,它会发现上述代码中的局部变量根本不会逃离方法的作用范围,就意味着这个对象不可能被共享,那么methodB()中的synchronized关键字也就显得没有意义了,**JIT即使编译器**就会将无意义的代码优化掉,也就是**锁消除**。 - 关闭锁消除优化 ``` -XX:-EliminateLocks ``` #五、偏向锁 顾名思义,他会偏向第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在别的线程来使用的情况,就会给线程加一个偏向锁。锁标记为1,没有偏向锁则为0。 如果在执行过程中,遇到了其他线程抢占锁,则会升级为轻量级锁。 JVM默认启用偏向锁,在竞争激烈的场合,偏向锁会增加系统负担。 **关闭偏向锁** ``` -XX:-UseBiasedLocking ``` JVM启用偏向锁时,默认会有4s的延迟。原因在于,系统刚启动时,一般数据的竞争是比较激烈的,此时启用偏向锁会降低性能。 **修改偏向锁延迟时间** ``` -XX:BiasedLockingStartupDelay=0 ```

你可能感兴趣的:(08给女朋友讲讲并发编程-轻量级锁、锁膨胀、自旋、锁消除、偏向锁)