Java锁的分类

锁汇总

image.png

锁大致可以分类为
1、公平锁 / 非公平锁
2、可重入锁 / 非可重入锁
3、独享锁 / 共享锁
4、乐观锁 / 悲观锁
5、分段锁

1、公平锁 / 非公平锁

公平锁就是严格按照线程启动的顺序来执行的,不允许其他线程插队执行的;而非公平锁是允许插队的。

默认情况下 ReentrantLock 和 synchronized 都是非公平锁。ReentrantLock 可以设置成公平锁。

2、重入锁 / 非重入锁

可重入锁的意义之一在于防止死锁。

可重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。

synchronized 和 ReentrantLock 都是可重入锁。

可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。与可重入锁相反,不可重入锁不可递归调用,递归调用就发生死锁。

非重入锁实现

import java.util.concurrent.atomic.AtomicReference;  
  
public class UnreentrantLockTest {  
   
  
    private static class UnreentrantLock {  
        private AtomicReference<Thread> owner = new AtomicReference<>();  
  
        public void lock() {  
            Thread currentThread = Thread.currentThread();  
  
            for (; ; ) {  
                System.out.println(owner);  
                if (owner.compareAndSet(null, currentThread)) {  
                    System.out.println(currentThread + " locked...");  
                    return;  
                }  
            }  
        }  
  
        public void unlock() {  
            Thread currentThread = Thread.currentThread();  
  
            owner.compareAndSet(currentThread, null);  
  
            System.out.println(currentThread + " unlock...");  
        }  
    }  
}

使用原子引用来存放线程,同一个线程两次调用lock()方法,如果不执行unlock()释放锁的话,第二次调用自旋的时候就会产生死锁,这个锁是不可重入的

3、独享锁 / 共享锁

独享锁(互斥锁):同时只能有一个线程获得锁。比如,ReentrantLock 是互斥锁,ReadWriteLock 中的写锁是互斥锁。

共享锁:可以有多个线程同时获得锁。比如,Semaphore、CountDownLatch 是共享锁,ReentrantReadWriteLock中的读锁是共享锁。

独占锁某个节点被唤醒之后,它只需要将这个节点设置成head就完事了,而共享锁不一样,某个节点被设置为head之后,如果它的后继节点是SHARED状态的,那么将继续通过doReleaseShared方法尝试往后唤醒节点,实现了共享状态的向后传播。

4、乐观锁 / 悲观锁

乐观锁

每次读取数据的时候总是认为不会被人拿数据,所以不去加锁,但是在更新的时候回去对比一下原来的值,看有没有被别人更改过。

适用场景:读操作比较多的时候,提高了吞吐量。

实现方式

  1. 版本号机制
  2. CAS

悲观锁

每次读取数据的 时候都认为其他人会修改数据,所以读取数据的时候就加了锁,这样别人想拿的时候就会阻塞,直到这个线程释放锁。Synchronized 就是用悲观锁实现的。

适用场景

适合于更新操作比较频繁的情况


CAS与Synchronized的使用情景:

1、对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。

2、对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。

补充: synchronized在jdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。

5、自旋锁 VS 适应性自旋锁

在许多场景中,同步资源的锁定时间很短,为了这一小段时间去切换线程,线程挂起和恢复现场的花费可能会让系统得不偿失。如果物理机器有多个处理器,能够让两个或以上的线程同时并行执行,我们就可以让后面那个请求锁的线程不放弃CPU的执行时间,看看持有锁的线程是否很快就会释放锁。

而为了让当前线程“稍等一下”,我们需让当前线程进行自旋,如果在自旋完成后前面锁定同步资源的线程已经释放了锁,那么当前线程就可以不必阻塞而是直接获取同步资源,从而避免切换线程的开销。这就是自旋锁。


image.png

Lock-free

Lock-Free 编程仅描述了代码所表述的性质,而没有限定或要求代码该如何编写,描述要在编程中尽量少使用这些锁结构,降低线程间互相阻塞的机会,以提高应用程序的性能。类同的概念还有 "Lockless" 和 "Non-Blocking" 等。


image.png

当我们准备要满足 Lock-Free 编程中的非阻塞条件时,有一系列的技术和方法可供使用,如原子操作(Atomic Operations)、内存栅栏(Memory Barrier)、避免 ABA 问题(Avoiding ABA Problem)等。那么我们该如何抉择在何时使用哪种技术呢?可以根据下图中的引导来判断。

image.png

https://www.cnblogs.com/gaochundong/p/lock_free_programming.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容