ReentrantLock是如何阻塞等待线程的?

 1 public class Test_Lock {
 2     static ReentrantLock lock = new ReentrantLock();
 3     static class Runner implements Runnable {
 4         @Override
 5         public void run() {
 6             lock.lock();
 7             System.out.println(Thread.currentThread().getName());
 8             lock.unlock();
 9         }
10
11     }
12     public static void main(String[] args) {
13         lock.lock();
14         Thread t1 = new Thread(new Runner());
15         t1.start();
16         Thread t2 = new Thread(new Runner());
17         t2.start();
18         lock.unlock();
19         System.out.println(lock.getHoldCount());
20     }
21 }

新建一个ReentrantLock对象,主线程第一次调用ReentrantLock/lock -> NonfairSync/lock

1 final void lock() {
2     if (compareAndSetState(0, 1))
3         setExclusiveOwnerThread(Thread.currentThread()); //主线程成功获取锁。
4     else
5         acquire(1); //子线程等待
6 }

子线程1调用ReentrantLock/lock -> NonfairSync/lock -> AbstractQueuedSynchronizer/acquire(1)

1 public final void acquire(int arg) {
2     if (!tryAcquire(arg) &&
3         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
4         selfInterrupt();
5 }

1) -> NonfairSync/tryAcquire -> Sync/nonfairTryAcquire
主线程已经获得锁,所以返回false。

 1 final boolean nonfairTryAcquire(int acquires) {
 2     final Thread current = Thread.currentThread();
 3     int c = getState();
 4     if (c == 0) {
 5         if (compareAndSetState(0, acquires)) {
 6             setExclusiveOwnerThread(current);
 7             return true;
 8         }
 9     }
10     else if (current == getExclusiveOwnerThread()) {
11         int nextc = c + acquires;
12         if (nextc < 0) // overflow
13             throw new Error("Maximum lock count exceeded");
14         setState(nextc);
15         return true;
16     }
17     return false;
18 }

2) -> AbstractQueuedSynchronizer/addWaiter -> AbstractQueuedSynchronizer/enq
把子线程加入等待队列,此时队列为空,所以直接enq(node)。

 1 private Node addWaiter(Node mode) {
 2     Node node = new Node(Thread.currentThread(), mode);
 3     // Try the fast path of enq; backup to full enq on failure
 4     Node pred = tail;
 5     if (pred != null) {
 6         node.prev = pred;
 7         if (compareAndSetTail(pred, node)) {
 8             pred.next = node;
 9             return node;
10         }
11     }
12     enq(node);
13     return node;
14 }

enq函数是死循环里面包含一个if else分支,
先进if分支,创建了一个没有数据的节点作为头结点,加入队列,
再入else分支,使用CAS将当前节点插入队列尾部(如果由于多线程的原因导致执行失败,则会继续下一次循环插入节点,直到插入成功)。
最后返回插入成入的节点。

 1 private Node enq(final Node node) {
 2     for (;;) {
 3         Node t = tail;
 4         if (t == null) { // Must initialize
 5             if (compareAndSetHead(new Node()))
 6                 tail = head;
 7         } else {
 8             node.prev = t;
 9             if (compareAndSetTail(t, node)) {
10                 t.next = node;
11                 return t;
12             }
13         }
14     }
15 }

3) -> AbstractQueuedSynchronizer/acquireQueued
该方法就是不停地判断阻塞线程的条件是否满足,一旦满足就阻塞线程。
还是个死循环,死循环内部包含两个if,第一个if,p == head 但是tryAcquire会失败(同1),所以第一个if不执行。
第二个if,-> AbstractQueuedSynchronizer/shouldParkAfterFailedAcquire
判断前驱节点的状态值(此时为0),所以会执行CAS设置前驱节点的状态为Node.SIGNAL(-1),并返回 false。
进入下一轮循环,再次 -> AbstractQueuedSynchronizer/shouldParkAfterFailedAcquire,此时返回true。

 1 final boolean acquireQueued(final Node node, int arg) {
 2     boolean failed = true;
 3     try {
 4         boolean interrupted = false;
 5         for (;;) {
 6             final Node p = node.predecessor();
 7             if (p == head && tryAcquire(arg)) {
 8                 setHead(node);
 9                 p.next = null; // help GC
10                 failed = false;
11                 return interrupted;
12             }
13             if (shouldParkAfterFailedAcquire(p, node) &&
14                 parkAndCheckInterrupt())
15                 interrupted = true;
16         }
17     } finally {
18         if (failed)
19             cancelAcquire(node);
20     }
21 }
22
23 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
24     int ws = pred.waitStatus;
25     if (ws == Node.SIGNAL)
26         /*
27          * This node has already set status asking a release
28          * to signal it, so it can safely park.
29          */
30         return true;
31     if (ws > 0) {
32         /*
33          * Predecessor was cancelled. Skip over predecessors and
34          * indicate retry.
35          */
36         do {
37             node.prev = pred = pred.prev;
38         } while (pred.waitStatus > 0);
39         pred.next = node;
40     } else {
41         /*
42          * waitStatus must be 0 or PROPAGATE.  Indicate that we
43          * need a signal, but don‘t park yet.  Caller will need to
44          * retry to make sure it cannot acquire before parking.
45          */
46         compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
47     }
48     return false;
49 }

然后 -> AbstractQueuedSynchronizer/parkAndCheckInterrupt
此时线程就阻塞在死循环中了。

1 private final boolean parkAndCheckInterrupt() {
2     LockSupport.park(this);
3     return Thread.interrupted();
4 }

那么线程是如何被唤醒的呢?

时间: 2024-08-20 02:53:30

ReentrantLock是如何阻塞等待线程的?的相关文章

等待线程结束(join)

很多情况下,线程之间的协作和人与人之间的协作非常相似.一种非常常见的合作方式就是分工合作.以我们非常熟悉的软件开发为例,在一个项目进行时,总是应该有几位号称是"需求分析师"的同事,先对系统的需求和功能点进行整理和总结,然后,以书面形式给出一份需求说明或者类似的参考文档,然后,软件设计师,研发工程师才会一拥而上,进行软件开发.如果缺少需求分析师的工作输出,那么软件研发的难度可能会比较大.因此,作为一名软件研发人员,总是喜欢等待需求分析师完成他应该完成的任务后,才愿意投身工作.简单地说,就

怎么在调用异步请求时不阻塞当前线程

在异步编程中,经常会调用已经写好的异步方法.这时会有一个需求:根据异步方法的返回值,做一些别的操作.这时会有两种实现方式: 1. 使用await,阻塞线程,等待异步方法的完成,然后获得返回值,再进行别的操作.示例: static void Main(string[] args) { Console.WriteLine("--------------程序运行开始-----------"); string url = "http://www.baidu.com"; va

终止阻塞的线程

线程状态 我们知道,一个线程可以处于以下四种状态之一: 1. 新建(New):当线程被创建时,它只会短暂地处于这种状态.此时它已经分配了必须的系统资源,并执行了初始化.此刻线程已经有资格获取CPU时间了,之后调度器将把这个线程转变为可运行状态或阻塞状态. 2. 就绪(Runnable):在这种状态下,只要调度器将CPU时间片分给线程,线程就可以运行.也就是说,在任意时刻,线程可以运行也可以不运行. 3. 阻塞(Blocked):线程能够运行,但有某个或多个条件阻止它运行.当线程处于阻塞状态时,调

Callable,阻塞队列,线程池问题

一.说说Java创建多线程的方法 1. 通过继承Thread类实现run方法   2. 通过实现Runnable接口 3. 通过实现Callable接口 4. 通过线程池获取 二. 可以写一个Callable的案例吗?如何调用Callable接口 /*是一个带返回值的多线程类,如果需要有线程返回的结果,就需要使用此类*/ class MyThread implements Callable<Integer> { @Override public Integer call() { return

等待线程的终止

在一些情形下,我们必须等待线程的终止.例如,我们的程序在执行其他的任务时,必须先初始化一些必须的资源.可以使用线程来完成这些初始化任务,等待线程终止,再执行程序的其他任务. 为了达到这个目的,我们使用Thread类的join()方法.当一个线程对象的join()方法被调用时,调用它的线程将被挂起,直到这个线程对象完成它的任务. package concurrency; import java.util.Date; import java.util.concurrent.TimeUnit; pub

编写多线程代码时,启动线程后等待线程结束方法

在编写多线程代码时,如果主线程结束,那么子线程也会随之结束,如何等待线程结束再往下执行.   等待线程执行完成代码.   线程代码:   package demo; import java.util.concurrent.CountDownLatch; public class NodeSqlThread1 implements Runnable{         private CountDownLatch cdlSync;         public NodeSqlThread1(Coun

windows核心编程之使用线程APC回调安全退出多个等待线程

前言 程序开发中经常遇到需要这些情况:辅助线程正在等待内核对象的触发,主线程需要强制终止辅助线程.我们常常做的就是使用:TerminateThread来强制终止线程.这样做当然是不太好的,强制终止线程后系统不会销毁此线程的堆栈,长久下去内存泄露问题就会很严重了.线程最安全的退出方式当然还是让它自己返回了.本文主要介绍windows核心编程中介绍的一种安全退出线程方式:使用可等待API等待内核对象触发,添加线程APC回调. API介绍 首先得简单介绍下一个重要的windows API DWORD

等待线程结束

正常环境下等待线程结束 如果需要等待线程结束,就在线程的实例对象上调用join().在管理线程之创建线程最后的例子中,用my_thread.join()代替my_thread.detach()就可以确保在函数终止前.局部变量析构前,线程会终止.在这种情况下,用分开的线程来运行函数就没有什么意义了.因为在等待my_thread终止时,这个线程就不做任何事情了.在实际的工程应用中,要么这个线程做自己的事,要么创建多个线程等待它们结束. join()是简单粗暴的,或者你等待线程终止,或者你不等待.如果

windows C++ 互相等待线程同步示例

windows C++ 互相等待线程同步示例 开发中遇到线程同步问题,可抽象为如下模型: 1.主线程的继续执行的前提是子线程的变量已经初始化完毕. 2.子线程待主线程执行完毕后继续执行后续的初始化操作. 重复1,2执行--直到通信结束. 如下图所示: 源码(VS2010编译.运行通过) #include "stdafx.h" #include <iostream> using namespace std; #include <windows.h> const c