一、介绍
1.1 简介
在Java1.5,Java并发包提供的一个实现Lock接口的可重入锁【ReentrantLock】, 内部采用AbstractQueuedSynchronizer 实现, 支持获取锁时的公平和非公平性选择。
1.2 相关概念
- 可重入锁:指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码。也就是对于相同的锁,同一线程可以获得多次。不然就会发生死锁问题。
- 可中断锁:在某些条件下可以相应中断的锁。在Java中,synchronized就不是可中断锁,而 Lock是可中断锁。
- 公平锁:公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。
- 非公平锁:无法保证锁的获取是按照请求锁的顺序进行的。有可能锁刚被释放,正好新来了个线程请求锁,这样后面等待的线程就不能获得锁。在极端情况下,可能导致一直无法获取锁。
注意: 对于非公平锁,这里我一度以为每次争取锁的过程都是非公平的。 其实不然,如果已在队列中排队的线程,是按照FIFO 顺序获取锁,但这是如果有一个新的线程来争取锁,那么在非公平锁模式下,这个新的线程可能会比在等待队列中排队的线程先获取到锁。
二、获取锁过程
2.1 获取锁流程图
获取锁的整体流程
非公平锁和公平锁的tryAcquire实现细节
2.2 ReentrantLock 内部结构
三个线程开始争取锁,因为是不公平锁,所以可以同时争取锁
当线程2获取到锁后,其它线程通过cas加入等待队列。自旋等待获取锁
如果线程2没有释放锁,再次访问共享资源时,因为当前占用锁的线程是线程2,所以只需要设置state加一
三、释放锁过程
3.1 释放锁流程
3.2 释放锁时AQS的变化
在线程2 释放锁后,队列中的线程在自旋中判断锁是否已释放,如果已释放且线程所在节点是头节点的下一个节点(如线程1),即可以获取锁。
四、Condition 条件锁
Java 官方提供的Condition例子
详情查看
public class BoundedBufferTest {
//锁
final Lock lock = new ReentrantLock();
//队列已满条件
final Condition notFull = lock.newCondition();
//队列已空条件
final Condition notEmpty = lock.newCondition();
//队列容器
final Object[] items = new Object[10];
//下标,和计数
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
//上锁
lock.lock();
try {
//如果当前数量count 等于容器最大容量
while (count == items.length)
//挂起写线程
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
//唤醒读线程
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
//当前数量count等于0
while (count == 0)
//挂起读线程
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
//唤醒写线程
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
BoundedBufferTest boundedBufferTest = new BoundedBufferTest();
new Thread(() -> {
try {
while (true) {
int i = new Random().nextInt(10000);
System.out.println("send =>" + i);
boundedBufferTest.put(i);
TimeUnit.SECONDS.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
while (true) {
Object take = boundedBufferTest.take();
System.out.println("<= receive " + take);
TimeUnit.SECONDS.sleep(5);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
4.1 await() 阻塞过程
- 整体流程图
- 锁内部结构
初始化时,内部结构
当线程开始运行时, 线程read 获取到锁,因为队列容器中没有元素,进而挂起线程read, 【notEmpty.await()】
当线程read成功释放锁后,线程write开始获取锁, 往容器中put元素
4.2 signal()唤醒过程
- 流程图
- 锁内部结构
参考
- https://www.processon.com/view/5df335fde4b004cc9a2dfa70
- https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/ReentrantLock.html
- https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Condition.html
- https://www.processon.com/special/template/58613652e4b032b0452c6e3a
- https://www.cnblogs.com/liqiangchn/p/12081838.html