一、介绍

1.1 简介

在Java1.5,Java并发包提供的一个实现Lock接口的可重入锁【ReentrantLock】, 内部采用AbstractQueuedSynchronizer 实现, 支持获取锁时的公平和非公平性选择。

1.2 相关概念

  • 可重入锁:指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码。也就是对于相同的锁,同一线程可以获得多次。不然就会发生死锁问题。
  • 可中断锁:在某些条件下可以相应中断的锁。在Java中,synchronized就不是可中断锁,而 Lock是可中断锁。
  • 公平锁:公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。
  • 非公平锁:无法保证锁的获取是按照请求锁的顺序进行的。有可能锁刚被释放,正好新来了个线程请求锁,这样后面等待的线程就不能获得锁。在极端情况下,可能导致一直无法获取锁。

注意: 对于非公平锁,这里我一度以为每次争取锁的过程都是非公平的。 其实不然,如果已在队列中排队的线程,是按照FIFO 顺序获取锁,但这是如果有一个新的线程来争取锁,那么在非公平锁模式下,这个新的线程可能会比在等待队列中排队的线程先获取到锁。

二、获取锁过程

2.1 获取锁流程图

获取锁的整体流程
image.png

非公平锁和公平锁的tryAcquire实现细节
image.png

2.2 ReentrantLock 内部结构

三个线程开始争取锁,因为是不公平锁,所以可以同时争取锁
image.png

当线程2获取到锁后,其它线程通过cas加入等待队列。自旋等待获取锁
如果线程2没有释放锁,再次访问共享资源时,因为当前占用锁的线程是线程2,所以只需要设置state加一
image.png

三、释放锁过程

3.1 释放锁流程

image.png

3.2 释放锁时AQS的变化

在线程2 释放锁后,队列中的线程在自旋中判断锁是否已释放,如果已释放且线程所在节点是头节点的下一个节点(如线程1),即可以获取锁。
image.png

四、Condition 条件锁

Java 官方提供的Condition例子
详情查看

  1. public class BoundedBufferTest {
  2. //锁
  3. final Lock lock = new ReentrantLock();
  4. //队列已满条件
  5. final Condition notFull = lock.newCondition();
  6. //队列已空条件
  7. final Condition notEmpty = lock.newCondition();
  8. //队列容器
  9. final Object[] items = new Object[10];
  10. //下标,和计数
  11. int putptr, takeptr, count;
  12. public void put(Object x) throws InterruptedException {
  13. //上锁
  14. lock.lock();
  15. try {
  16. //如果当前数量count 等于容器最大容量
  17. while (count == items.length)
  18. //挂起写线程
  19. notFull.await();
  20. items[putptr] = x;
  21. if (++putptr == items.length) putptr = 0;
  22. ++count;
  23. //唤醒读线程
  24. notEmpty.signal();
  25. } finally {
  26. lock.unlock();
  27. }
  28. }
  29. public Object take() throws InterruptedException {
  30. lock.lock();
  31. try {
  32. //当前数量count等于0
  33. while (count == 0)
  34. //挂起读线程
  35. notEmpty.await();
  36. Object x = items[takeptr];
  37. if (++takeptr == items.length) takeptr = 0;
  38. --count;
  39. //唤醒写线程
  40. notFull.signal();
  41. return x;
  42. } finally {
  43. lock.unlock();
  44. }
  45. }
  46. public static void main(String[] args) {
  47. BoundedBufferTest boundedBufferTest = new BoundedBufferTest();
  48. new Thread(() -> {
  49. try {
  50. while (true) {
  51. int i = new Random().nextInt(10000);
  52. System.out.println("send =>" + i);
  53. boundedBufferTest.put(i);
  54. TimeUnit.SECONDS.sleep(1);
  55. }
  56. } catch (InterruptedException e) {
  57. e.printStackTrace();
  58. }
  59. }).start();
  60. new Thread(() -> {
  61. try {
  62. while (true) {
  63. Object take = boundedBufferTest.take();
  64. System.out.println("<= receive " + take);
  65. TimeUnit.SECONDS.sleep(5);
  66. }
  67. } catch (InterruptedException e) {
  68. e.printStackTrace();
  69. }
  70. }).start();
  71. }
  72. }

4.1 await() 阻塞过程

  • 整体流程图

image.png

  • 锁内部结构

初始化时,内部结构
image.png
当线程开始运行时, 线程read 获取到锁,因为队列容器中没有元素,进而挂起线程read, 【notEmpty.await()】
image.png
当线程read成功释放锁后,线程write开始获取锁, 往容器中put元素

4.2 signal()唤醒过程

  • 流程图

image.png

  • 锁内部结构

参考