ReenTrantLock详解

首先介绍下各个参数的含义;

Node节点的参数含义:

关于SIGNAL、CANCELLED、CONDITION、PROPAGATE四个状态,JDK源码的注释中同样有了详细的解读,再用一张表格总结一下:

 

接下来查看源码,以下所有备注都写在代码中,主要本人也没写过什么博客,文字功底比较差,大家见谅。

下面说到的源码都采用非公平锁来讲解:

首先说下lock()方法

复制代码

1 final void lock() {
2             // 设置state状态为1
3             if (compareAndSetState(0, 1))
4                 // 设置成功则设置当前独占线程为自己
5                 setExclusiveOwnerThread(Thread.currentThread());
6             else
7                 // 表示已有其他线程抢到锁了,需要下一步操作
8                 acquire(1);
9         }    

复制代码

acquire()方法

复制代码

1 public final void acquire(int arg) {
2         // tryAcquire 重新抢占锁,可能此时锁已被释放
3         // 若抢占锁失败,通过addWaiter方法加入的等待队列
4         // acquireQueued,在等待队列中尝试抢占锁
5         if (!tryAcquire(arg) &&
6             acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
7             // 若标志线程中断了,则调用中断线程操作
8             selfInterrupt();
9     }    

复制代码

先说tryAcquire(arg)源码,因使用的是非公平锁,则从第5行的tryAcquire(arg)定位到nonfairTryAcquire方法:

复制代码

 1 final boolean nonfairTryAcquire(int acquires) {
 2             final Thread current = Thread.currentThread();
 3             int c = getState();
 4             // 若c == 0,标志当前已无线程占用着锁,则尝试通过CAS更新state值,更新成功则抢占锁成功,设置当前线程为自己
 5             if (c == 0) {
 6                 if (compareAndSetState(0, acquires)) {
 7                     setExclusiveOwnerThread(current);
 8                     return true;
 9                 }
10             }
11             // 若当前线程与已抢占锁的线程为同一线程,表示是重入锁,即同一个线程可以多次获取锁,此时需要将state+1
12             // 当unlock也需要将state-1,直到state=0时,其他线程才有机会获取锁
13             // 其中重入锁的次数最多是Integer.Max_value值,从下面代码中即可看出
14             else if (current == getExclusiveOwnerThread()) {
15                 int nextc = c + acquires;
16                 if (nextc < 0) // overflow
17                     throw new Error("Maximum lock count exceeded");
18                 setState(nextc);
19                 return true;
20             }
21             return false;
22         }

复制代码

addWaiter方法:

复制代码

 1 // 将当前线程放入等待队列中去
 2 private Node addWaiter(Node mode) {
 3         Node node = new Node(Thread.currentThread(), mode);
 4         // Try the fast path of enq; backup to full enq on failure
 5         Node pred = tail;
 6         // 若尾部节点tail不为空,则直接将自己的Node放在tail后面即可
 7         if (pred != null) {
 8             node.prev = pred;
 9             if (compareAndSetTail(pred, node)) {
10                 pred.next = node;
11                 return node;
12             }
13         }
14         // 若尾部节点为空,或者放在tail后面失败了,则通过enq方法将Node放入等待队列中
15         enq(node);
16         return node;
17     }

复制代码

enq(node)方法:

复制代码

 1 // 通过for循环实现自旋功能,一旦tail节点为空或者设置尾部节点失败,都会重试,保证将Node放入等待队列中
 2 private Node enq(final Node node) {
 3         for (;;) {
 4             Node t = tail;
 5             // 若尾部节点为空,则将空的head节点赋值给tail节点,下次循环就进入else处理
 6             if (t == null) { // Must initialize
 7                 if (compareAndSetHead(new Node()))
 8                     tail = head;
 9             } else {
10                 // 将Node节点放正在tail节点的后面,并将tail节点的next节点设置成Node
11                 node.prev = t;
12                 if (compareAndSetTail(t, node)) {
13                     t.next = node;
14                     return t;
15                 }
16             }
17         }
18     }

复制代码

addWaiter处理完毕了,将已放入等待队列的Node作为参数传给acquireQueued方法处理:

复制代码

 1 // 在等待队列中再次尝试获取锁,失败了才阻塞
 2 final boolean acquireQueued(final Node node, int arg) {
 3         boolean failed = true;
 4         try {
 5             boolean interrupted = false;
 6             for (;;) {
 7                 // 获取Node节点的上一个节点
 8                 final Node p = node.predecessor();
 9                 // 若Node节点的上一个节点是head节点,则尝试获取锁
10                 // setHead(node);获取锁成功,则将自己设置为head节点
11                 //  p.next = null;将之前的head节点设置为空,方便GC回收
12                 //  failed = false;已成功获取锁,因不需要finally中的取消抢占锁操作
13                 if (p == head && tryAcquire(arg)) {
14                     setHead(node);
15                     p.next = null; // help GC
16                     failed = false;
17                     return interrupted;
18                 }
19                 // shouldParkAfterFailedAcquire 下面说明
20                 // parkAndCheckInterrupt 阻塞当前线程
21                 if (shouldParkAfterFailedAcquire(p, node) &&
22                     parkAndCheckInterrupt())
23                     interrupted = true;
24             }
25         } finally {
26             // 当failed为true时,表示当前线程在这里执行超时或者中断了,才会导致for循环执行结束
27             // 因为只有一个地方设置了返回值的操作,并在返回值前设置failed为false
28             if (failed)
29                 cancelAcquire(node);
30         }
31     }

复制代码

shouldParkAfterFailedAcquire方法:

复制代码

 4 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
 5         int ws = pred.waitStatus;
 6         if (ws == Node.SIGNAL)
 7             // 表示当前节点通知了他的上个节点下次通知我,当前节点应被阻塞
 8             return true;
 9         if (ws > 0) {
10             // pred节点被取消了,则从pred节点之前的节点开始直到取到waitStatus的值<=0的节点,此时pred节点已被赋值为waitStatus<=0的节点
11             // 并且此时将取消的Node与其他Node依赖关系去除了,GC可以回收了
12             do {
13                 node.prev = pred = pred.prev;
14             } while (pred.waitStatus > 0);
15             pred.next = node;
16         } else {
17             // 因为当前节点的上一个节点的waitStatus=-1时,当前节点才能被阻塞,因此将其他状态节点设置为-1
18             // 而即使因并发,CAS设置失败了,也不影响,下次循环进来还可以继续处理
19             compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
20         }
21         return false;
22     }

复制代码

parkAndCheckInterrupt方法比较简单:
1 // 调用LockSupport的park方法阻塞线程,并返回当前线程的阻塞状态
2 private final boolean parkAndCheckInterrupt() {
3         LockSupport.park(this);
4         return Thread.interrupted();
5     }

以下举例都以在分析非公平锁:

一、 线程1(name=Thread1)调用lock()方法

  state = 1

  exclusiveOwnerThread = Thread1

二、 线程1不释放锁的情况下,线程2(name=Thread2)调用lock()方法

  1 由于state=1,compareAndSetState更新失败

  2 执行acquire(arg)

    2.1 执行tryAcquire(arg),尝试再一次获取锁,失败

    2.2 addWaiter(Node.EXCLUSIVE)添加到等待队列尾部,因此时head节点还没,会初始化一个空的head节点在线程2前面

      head->node(线程2)

    2.3 acquireQueued(node,arg)再等待队列中尝试获取锁 进入for循环

      2.3.1 if (p == head && tryAcquire(arg))由于线程2的上一个节点是head,尝试获取锁,失败

      2.3.2 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())

        2.3.2.1 shouldParkAfterFailedAcquire(p, node)由于head是空节点,则waitStatus=0,更新waitStatus=-1,返回false,重新循环

        2.3.2.2 再次执行2.3.1,尝试获取锁,失败(在这一步有个细节,相当于自旋操作,waitStatus默认为0,在设置为-1后还会再尝试获取锁,因为阻塞线程 会引起上下文切换,影响性能,多做一次获取锁的操作能减少性能的损耗)

        2.3.2.3 再一次来到shouldParkAfterFailedAcquire(p, node),此时由于waitStatus=-1,返回true

        2.3.2.4 parkAndCheckInterrupt()阻塞当前线程,就不执行下面的返回中断状态的程序代码了,线程2就挂起了

    2.4 selfInterrupt()这里其实不会执行,因为线程2已经阻塞了

三、线程1(name=Thread1)调用unlock()方法

  1 tryRelease(arg)将state减1

  2 unparkSuccessor(head)

    2.1 若head.waitStatus<0,CAS设置waitStatus=0

    2.2 将离head节点最近的一个waitStatus<=0且不为空的节点unpark,即不阻塞了 state = 0 exclusiveOwnerThread = null

四、由于线程1释放锁了,且将线程2设置为不阻塞了,下面看线程2的行为

  1 回到(二)的2.3.2.4,线程2继续执行下去,返回中断状态 继续for循环

  2 if (p == head && tryAcquire(arg)),此时能获取锁了,将state=1,exclusiveOwnerThread=Thread2 state = 1 exclusiveOwnerThread = Thread2

这样从一到四,就完成了线程1获取锁,线程2阻塞,线程1释放锁,线程2获取锁的整个步骤

 

整理几个结论:

1.waitStatus=-1的节点的next节点才会被阻塞;

2.已经被取消的节点会在acquireQueued(node,arg)步骤中被放弃,GC会去回收;

3.释放锁的线程所占用的节点会在下个线程获取锁的时候被释放掉;

4.线程只有在head节点的next节点才会去尝试获取锁,获取不到锁就会阻塞;

 

疑问:

1.当线程2获取锁之后,会将自己设置为head节点,并把线程1的节点设置为空,帮助GC回收线程1的Node, 也就意味着,线程1在释放锁之后不需要自己去清除自己的状态,会有其他线程会来做这一步操作, 而如果没有其他线程进来,我想在unparkSuccessor那一步中,将head节点的waitStatus状态设置为0, 是不是告诉让GC来回收,不知道我理解的对不对?

 

下面说下公平锁与非公平锁的区分:

公平锁的lock方法:

1 final void lock() {
2             acquire(1);
3         }

非公平锁的lock方法:

1 final void lock() {
2             if (compareAndSetState(0, 1))
3                 setExclusiveOwnerThread(Thread.currentThread());
4             else
5                 acquire(1);
6         }

公平锁在tryAcquire(arg)方法中多了一个判断条件

复制代码

 1 // 表示只有在head节点的next节点获取队列中只有head和tail节点才可以去尝试获取锁
 2 // 意思就是按队列顺序来执行,此方法用于公平锁的实现
 3 public final boolean hasQueuedPredecessors() {
 4         // The correctness of this depends on head being initialized
 5         // before tail and on head.next being accurate if the current
 6         // thread is first in queue.
 7         Node t = tail; // Read fields in reverse initialization order
 8         Node h = head;
 9         Node s;
10         return h != t &&
11             ((s = h.next) == null || s.thread != Thread.currentThread());
12     }

复制代码

 

其实就是非公平锁会优先更新state状态,不会在等待队列中去获取锁,公平锁在尝试获取锁之前还要判断当前线程是否是在head节点的next节点位置。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值