常见的锁策略

一、悲观锁 and 乐观锁

1、悲观锁

现有一把锁,有100个线程同时竞争这把锁,每一个线程加锁的频率都很高,一个线程尝试加锁时,另一个线程大概率会占有这把锁;

解决方案:使用重量级锁,由于悲观锁的竞争很激烈,就导致线程阻塞时间过长,效率更低下;

2.乐观锁

现有一把锁,但只有10个线程竞争这把锁,其中的每一个线程都会有很的概率会拿到这把锁,与其他线程竞争的激烈程度就会小很多;

解决方案:使用轻量级锁,线程之间的竞争更小,效率更高;

二、等待挂起锁 and 自旋锁

1、等待挂起锁

操作系统内核级别的,当一个线程再竞争锁时,这个锁被另一个线程占用着,那么这个线程就会进入阻塞状态,后续需要内核唤醒,在阻塞期间不会消耗CPU资源,但当其他线程释放该锁时,这个线程不会第一时间获取到这把锁,就导致获取锁的周期很长;

2.自旋锁

应用程序级别的,当这个线程竞争锁时,该锁被其他线程占用着,那么这个线程就会进入忙等状态,在忙等的过程中,会一直消耗CPU资源,但由于这个线程是一直在执行的状态,那么当别的线程释放锁时,这个线程就会第一时间获取到,于是获取锁的周期就很短;

三、普通互斥锁 and 读写锁

1、普通互斥锁

即synchronized,无论是读操作还是写操作,都会加锁,但是单纯的读操作并不存在线程安全问题,于是就会使得程序运行的效率下降;

2、读写锁

与普通互斥锁相对,当两个线程都是读操作时,就不会产生阻塞,当两个线程一读一写或是均为写就会产生线程阻塞,这样就能减少不必要的线程阻塞,提高线程运行效率;

四、公平锁 and 非公平锁

1、公平锁

按照线程的先来后到顺序进行调用;

ReentrantLock实现了公平锁,但默认是非公平的,若使用公平锁,就要在创建ReentrantLock实例时传入true;

2、非公平锁

按照随机顺序进行调度,synchronized也是非公平锁;

五、可重入锁 and 不可重入锁

1、可重入锁

即一个线程可以多次获取同一把锁,有下面的代码:

Thread thread = new Thread(() -> {
    synchronized(locker) {
        
        synchronized(locker) {
           //...
        }

    }

});

thread获取locker后再次获取了locker,不会引发阻塞,代码继续向下执行;

2.不可重入锁

当thread获取locker后(还未释放locker)再次获取locker,就会引发线程阻塞等待,这时就会产生死锁;

六、偏向锁(懒汉模式)

当synchronized加锁时,第一时间不是真的加锁,而是做了一个标记,这种方法相对于加锁更高高效,当发现有别的线程尝试竞争这把锁时,synchronized就会第一时间把锁给前一个线程加上,此时偏向锁转化为轻量级锁,其他线程只能阻塞等待;若直到该线程的代码执行完都没有其他线程来竞争这把锁,这时synchronized就会消除标记,不涉及真加锁与真解锁;

七、锁消除

编译器会判断这段代码是否需要加锁,若不需要加锁却加上了锁,就会将synchronized去掉,即编译器优化;

锁的粒度:加锁与解锁之间,若代码执行的指令或时间越多,就称锁的粒度越粗,反之越细;

在一段代码中,若反复针对细粒度的代码短进行加锁,就会被编译器优化为更粗粒度的加锁,抽象图如下:

常见的锁策略_第1张图片

八、synchronized对应的锁策略

synchronized是自适应锁(悲观锁与乐观锁),可重入锁,普通互斥锁,偏向锁,非公平锁;

synchronized的自适应过程:

无锁 -> 偏向锁 -> 轻量级锁 / 自旋锁 -> 重量级锁 / 挂起等待锁

你可能感兴趣的:(java,开发语言)