ReentrantLock lock和unlock源码分析

ReentrantLock$NonfairSync#lock方法详解

总结性概述

  1. 获取锁操作:CAS原子操作state(0->1)
  2. 获取失败后,先进先出尾部插入CLH队列中
  3. 通过CLH队列的前缀节点的waitStatus状态是否Node.SIGNAL决定是否阻塞:LockSupport.park
  4. LockSupport.park响应中断,但不抛出InterruptedException异常
  5. CLH队列的头节点是占有锁的节点,如果其waitStatus不等于0,则说明后面有排队的
源码调式demo介绍
 @Test
    public void testLockAndUnlock() throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    lock.lock();
                    System.out.println("断点中,模拟占有锁");//1
                }  finally {
                    lock.unlock();//3
                }
            }
        }, "thread1");

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    lock.lock();//2
                    System.out.println("阻塞释放");
                }  finally {
                    lock.unlock();
                }
            }
        }, "thread2");
        thread1.start();
        thread2.start();
        Thread.sleep(1000 * 60 * 30);
    }

IDEA线程断点模式

  1. 启用IDEA的线程端点模式
    1.1 在thread1中1处断点,模拟thread1占有锁但未释放
    1.2 切换到thread2时,到lock.lock()时会因未获取锁而阻塞
根据源码分析加锁原理

在这里插入图片描述

  1. 非公平锁体现if逻辑块里,在入队前先尝试获取锁
    1.1 获取锁主要CAS操作,将state属性原子性从0修改为1,然后设置获取锁的线程(方便后面重入操作)
    在这里插入图片描述
尝试获取锁ReentrantLock$Sync#nonfairTryAcquire

在这里插入图片描述

  1. 上图红色部分体现重入锁的概念,同一线程可以多次加锁
CLH 思想的实现 AbstractQueuedSynchronizer#addWaiter

在这里插入图片描述
在这里插入图片描述
维护链表

  1. 将当前线程信息封装成Node
  2. enq(node) 自旋操作
    2.1 tail节点初始化为null ,进行CAS操作初始化,tail = head = new Node()
    2.2 操纵pre、next指针维护CLH链表(尾部插入)
以独占不可中断模式获取已经在队列中的线程(用于condition wait以及acquire)

在这里插入图片描述

  1. 获取当前Node的前置节点
    1.1 如果是头节点则尝试获取锁(失败,是头但未释放)
    1.2 获取锁成功后,将次node设为head
    1.3 将之前的head从链表中移除(p.next = null)
  2. 判断是否应该阻塞:shouldParkAfterFailedAcquire(p, node)
    2.1 从尾一直往前找,直到找到waitStatus<=0的node
    2.2 waitStatus不等于-1(Node.SIGNAL),会初始化成-1
    2.3 当waitStatus=Node.SIGNAL时,返回true
  3. 阻塞并检查是否中断:parkAndCheckInterrupt()
    3.1 通过 LockSupport.park(this)进行阻塞
    (1)响应中断,但不会抛InterruptedException异常
    (2)当释放锁时 LockSupport.unpark(thread1),会解除阻塞继续执行
    3.2 Thread.interrupted() 检测是否中断,并清除中断位

========== 至此加锁过程分析完成 ======================

AbstractQueuedSynchronizer#release方法详解

在这里插入图片描述

  1. 尝试释放锁
    在这里插入图片描述
    1.1 释放过程:state - 1 是否等于0
    (1)是则可以释放,将独占线程属性exclusiveOwnerThread置为null
  2. 如果head节点(加锁线程节点)waitStatus不等于0,则说明后面有排队的node
    在这里插入图片描述
    2.1 避免释放,先将其waitStatus设置为0
    2.2 如果头节点的下一节点的waitStatus>0(取消),则从尾往头找,找到最靠近头的waitStatus <= 0的节点(为什么尾部寻址呢?)
    2.3 如果找到,则取消阻塞状态:LockSupport.unpark(s.thread);

疑问点

  1. 为什么中断线程后复位中断, LockSupport.park(lock)将执行两次才会阻塞?
    1.1 demo演示
    在这里插入图片描述
    在这里插入图片描述
    (1)场景还原
    1. thread1通过LockSupport.park(lock)阻塞住
    2. thread2中将thread1中断:thread1.interrupt();
      2.1 thread1的LockSupport.park(lock)响应中断,解除阻塞继续执行
      2.2 thread1恢复中断位:Thread.interrupted();
      (2)结果显示:及时恢复了中断位后,LockSupport.park(lock);执行了一次后再阻塞
  2. 解锁时,为什么要尾部寻址找到最靠近head的节点,然后再通知
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值