锁的分类优化、CAS、Synchronized底层实现

锁的分类优化、CAS、Synchronized底层实现

分类

偏向锁:没有线程跟我争抢资源的,加个标记就可以了。

轻量锁:竞争时间比较小,比如使用CAS就可以解决的。

重量锁:竞争时间开销很大,需要利用操作系统的同步机制


乐观锁:对目标资源不加任何锁,失败了,可以接着尝试

悲观锁:当我要做操作时,我必须将资源锁起来,避免别人来干扰


可重入锁:我是当前资源锁的拥有者,我再次获取的时候,不需要释放再获取。例如ReentrantLock

不可重入锁:即使我是当前资源锁的拥有者,但是我再次获取这把锁,需要释放再获取


自旋锁:线程在当前资源获取锁失败后,并不直接阻塞或者等待,利用循环不断尝试获取锁

非自旋锁:和自旋锁相反的呗~拿不到锁直接放弃,进入等待或者阻塞状态


公平锁:先来先得的意思。拿不到锁的线程会进入等待状态,开始排队,一般等待时间最久的先获得锁。

非公平锁:插队了


不可中断锁:一旦开始进入竞争锁的状态,就不可以中途退出,不能说我不要锁了,不想等待了,比如synchronized

可中断锁:可以随时退出竞争锁的状态,例如Reentrant Lock

jvm做的相关优化

自适应的自旋锁:jvm已经都优化好了,不用自己实现。自旋锁一直重复获取锁,其实对cpu消耗还是很大的。所以后来优化成了自适应的自旋锁,可以根据获取的成功率失败率,当前锁拥有者的状态等等,来决定自旋的周期时间等等。

还有锁消除、粗化之类的一系列优化~

CAS

乐观锁的一种,它不会对目标资源加锁。它的过程看图吧

在这里插入图片描述

1.线程A获取到当前的内存值V为1,与自己的预期值1相等,将资源修改为2。

2.因为CAS对目标资源并不加任何锁,所以会发生A在修改前,B先完成了修 改,这样A获取到的当前内存值V就

​ 为3,与自己的期望值A 1不相等,不修改。

使用场合

concurrentHashMap在没有产生hash碰撞时,直接插入数组,会使用CAS

原子类AtomicInteger

synchronize底层实现

底层是通过monitor锁来实现的。synchronized在jvm中的锁优先级 偏向锁---->轻量锁—>重量锁

1.同步代码块的实现

代码如下:

public class Atest {
    public  void test(){
        synchronized(this){
            System.out.println("meng");
        }
    }
}

反编译结果

在这里插入图片描述

这里呢~我们可以看到一个monitorenter,两个monitorexit指令。

monitorenter指令获得锁,monitorexit释放锁。为啥俩monitorexit这个呢~因为jvm要保证线程正确结束时执行释放锁,遇到异常结束的话也要执行,所以jvm会在这两个位置分别插入一个monitorexit指令。

monitor锁存在于对象的对象头中,所以这也就是为啥说,一个Java对象可以作为锁。同时,每个对象维护着一个计数器,初始值0。monitorenter命令会使计数器+1。monitorexit释放锁,使计数器-1。

线程A来获取monitor锁,首先判断计数器是否为0,为0则获取锁,计数器+1。此时,线程B来获取锁发现计数器为1,则直接进入阻塞等待状态。

线程A在已拥有锁的情况下,再次申请获取锁,计数器会再次累加+1。只有执行monitorexit将计数器-1,直至为0时,其他阻塞等待的线程就可以来获取这把锁了。

2.同步方法

代码如下:

public class BTest {
    public synchronized void test(){
        System.out.println("mengmeng");
    }
}

反汇编结果

在这里插入图片描述

同步方法与同步代码块不同,同步方法会增加一个ACC_SYNCHRONIZED 标识。jvm通过该标识来判断该方法是否为一个同步方法。然后来执行同步调用,获取monitor锁,执行方法,释放monitor锁。之后其余等待阻塞线程可再次获取锁。

猜你喜欢

转载自blog.csdn.net/weixin_44969687/article/details/106947162