ReentrantLock底层原理
一. 相关知识
在了解ReentrantLock之前先了解一些相关知识
1.1 公平锁和非公平锁
1)公平锁: 多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。
- 优点:所有的线程都能得到资源,不会饿死在队列中。
- 缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。
2) 非公平锁: 多个线程不会按照申请顺序获取锁,多线程会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。因此是不公平的。
- 优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
- 缺点:这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。
1.2 可重入锁
- 可重入锁的含义就是某个线程持有某个锁的时候,当该线程再次获取这个锁的时候,不会造成死锁。
- 简单来说就是:拥有该锁的同时再次获取该锁,不会造成死锁。
- 什么时候会出现上述这种情况呢?例如:
- 一个类中有多个同步方法(A,B)
- 一个线程要执行A方法,那么就要获取这个类对象锁,此时已经获取成功了
- 在执行A方法的过程中又要去调用B方法,由于B方法也是该类的同步方法,因此该线程又要去尝试获取该类对象锁。
- 这就是拥有该锁再获取该锁的情况
1.3 CAS操作
- CAS是一种无锁算法。有3个操作数:内存值V、旧的预期值A、要修改的新值B。
- 当我们要修改内存值的时候要进行判断,当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
1.4 AQS队列
- AQS是一个用于构建锁和同步容器的框架。
- AQS使用一个FIFO的队列(也叫CLH队列,是CLH锁的一种变形),表示排队等待锁的线程。队列头节点称作“哨兵节点”或者“哑节点”,它不与任何线程关联。其他的节点与等待线程关联,每个节点维护一个等待状态waitStatus。结构如下图所示:
二. ReentrantLock的锁流程
- ReentrantLock先通过CAS尝试获取锁,如果获取了就将锁状态state设置为1
- 如果此时锁已经被占用,
- 被自己占用:判断当前的锁是否是自己占用了,如果是的话就锁计数器会state++(可重入性)
- 被其他线程占用:该线程加入AQS队列并wait()
- 当前驱线程的锁被释放,一直到state==0,挂在CLH队列为首的线程就会被notify(),然后继续CAS尝试获取锁,此时:
- 非公平锁,如果有其他线程尝试lock(),有可能被其他刚好申请锁的线程抢占
- 公平锁,只有在CLH队列头的线程才可以获取锁,新来的线程只能插入到队尾。
(注:ReentrantLock默认是非公平锁,也可以指定为公平锁)
三. ReentrantLock的使用
- ReentrantLock的使用是通过lock()和 unlock()方法实现的
- 具体底层实现方法就是通过大量的CAS操作+AQS队列去维护state的状态。
具体lock和unlock的使用这里就不做阐述了,无非就是获取锁和释放锁的两个函数。
四.总结
- ReentrantLock底层通过大量的CAS操作和AQS队列去维护state变量的状态去实现线程安全的功能。
- ReentrantLock锁流程就是先通过CAS操作尝试修改state状态获取锁,如果获取失败就判断当前占用锁的是不是自身,如果是的话就进行重入。如果不是就进入AQS队列等待。
- ReentrantLock默认是非公平锁,也可以设置为公平锁使用,那么就要维护AQS队列
- ReentrantLock是可重入锁