【搞定Java并发编程】第19篇:重入锁 --- ReentrantLock 详解

  • AQS系列文章:

1、队列同步器AQS源码分析之概要分析

2、队列同步器AQS源码分析之独占模式

3、队列同步器AQS源码分析之共享模式

4、队列同步器AQS源码分析之Condition接口、等待队列

  • 先推荐两篇好文章:

1、深入剖析基于并发AQS的(独占锁)重入锁(ReetrantLock)及其Condition实现原理【写的非常好】

2、 ReentrantLock源码分析

说明:本文内容大部分均出自与上面这两篇文章之中。


本文目录:

1、重入锁ReentrantLock的基本概念

2、重入锁ReentrantLock与synchronized关键字的对比

3、重入锁的一个简单案例

4、重入锁ReentrantLock的源码分析

4.1、获取锁和释放锁

4.2、公平锁和非公平锁

5、等待队列的实现机制


1、重入锁ReentrantLock的基本概念

重入锁ReentrantLock,顾名思义,就是支持重进入的锁,它表示该线程能够支持一个线程对资源的重复加锁。除此之外,重入锁还支持获取锁时的公平和非公平性选择。

所谓的公平与非公平指的是在请求先后顺序上,先对锁进行请求的就一定先获取到锁,那么这就是公平锁,反之,如果对于锁的获取并没有时间上的先后顺序,如后请求的线程可能先获取到锁,这就是非公平锁,一般而言非,非公平锁机制的效率往往会胜过公平锁的机制,但在某些场景下,可能更注重时间先后顺序,那么公平锁自然是很好的选择。需要注意的是ReetrantLock支持对同一线程重加锁,但是加锁多少次,就必须解锁多少次,这样才可以成功释放锁。

ReetrantLock是基于AQS并发框架实现的。这里简单回顾下AQS的工作原理:

AbstractQueuedSynchronizer又称为队列同步器(后面简称AQS),它是用来构建锁或其他同步组件的基础框架,内部通过一个 int 类型的成员变量 state 来控制同步状态,当 state=0 时,则说明没有任何线程占有共享资源的锁,当 state=1 时,则说明有线程目前正在使用共享变量,其他线程必须加入同步队列进行等待。AQS内部通过内部类 Node 构成 FIFO 的同步队列来完成线程获取锁的排队工作,同时利用内部类 ConditionObject 构建等待队列,当 Condition 调用 wait() 方法后,线程将会加入等待队列中,而当 Condition 调用 signal() 方法后,线程将从等待队列转移动同步队列中进行锁竞争。注意这里涉及到两种队列,一种的同步队列,当线程请求锁而等待的后将加入同步队列等待,而另一种则是等待队列(可有多个),通过Condition调用await()方法释放锁后,将加入等待队列。

AQS作为基础组件,对于锁的实现存在两种不同的模式,即共享模式(如Semaphore)和独占模式(如ReetrantLock),无论是共享模式还是独占模式的实现类,其内部都是基于AQS实现的,也都维持着一个虚拟的同步队列,当请求锁的线程超过现有模式的限制时,会将线程包装成Node结点并将线程当前必要的信息存储到node结点中,然后加入同步队列等待获取锁,而这一系列操作都是AQS协助我们完成的。这也是AQS作为基础组件的原因,无论是Semaphore还是ReetrantLock,其内部绝大多数方法都是间接调用AQS完成的。

下面就看下ReentrantLock 和 AQS 之间的关系:

  • AbstractOwnableSynchronizer:抽象类,定义了存储独占当前锁的线程和获取的方法。
  • AbstractQueuedSynchronizer:抽象类,AQS框架核心类,其内部以虚拟队列的方式管理线程的锁获取与锁释放,其中获取锁(tryAcquire方法)和释放锁(tryRelease方法)并没有提供默认实现,需要子类重写这两个方法实现具体逻辑,目的是使开发人员可以自由定义获取锁以及释放锁的方式。
  • Node:AbstractQueuedSynchronizer 的内部类,用于构建虚拟队列(链表双向链表),管理需要获取锁的线程。
  • Sync:抽象类,是ReentrantLock的内部类,继承自AbstractQueuedSynchronizer,实现了释放锁的操作(tryRelease()方法),并提供了lock抽象方法,由其子类实现。
  • NonfairSync:是ReentrantLock的内部类,继承自Sync,非公平锁的实现类。
  • FairSync:是ReentrantLock的内部类,继承自Sync,公平锁的实现类。
  • ReentrantLock:实现了Lock接口的,其内部类有Sync、NonfairSync、FairSync,在创建时可以根据fair参数决定创建NonfairSync(默认非公平锁)还是FairSync。

ReentrantLock内部存在3个实现类,分别是Sync、NonfairSync、FairSync。其中Sync继承自AQS实现了解锁tryRelease()方法,而NonfairSync(非公平锁)、 FairSync(公平锁)则继承自Sync,实现了获取锁的tryAcquire()方法。ReentrantLock的所有方法调用都通过间接调用AQS和Sync类及其子类来完成的。

从上述类图可以看出AQS是一个抽象类,但请注意其源码中并没一个抽象的方法,这是因为AQS只是作为一个基础组件,并不希望直接作为直接操作类对外输出,而更倾向于作为基础组件,为真正的实现类提供基础设施。如构建同步队列,控制同步状态等,事实上,从设计模式角度来看,AQS采用的模板模式的方式构建的,其内部除了提供并发操作核心方法以及同步队列操作外,还提供了一些模板方法让子类自己实现,如加锁操作以及解锁操作,

为什么这么做?这是因为AQS作为基础组件,封装的是核心并发操作,但是实现上分为两种模式,即共享模式与独占模式,而这两种模式的加锁与解锁实现方式是不一样的,但AQS只关注内部公共方法实现并不关心外部不同模式的实现,所以提供了模板方法给子类使用,也就是说实现独占锁,如ReentrantLock需要自己实现tryAcquire()方法和tryRelease()方法,而实现共享模式的Semaphore,则需要实现tryAcquireShared()方法和tryReleaseShared()方法,这样做的好处是显而易见的,无论是共享模式还是独占模式,其基础的实现都是同一套组件(AQS),只不过是加锁解锁的逻辑不同罢了,更重要的是如果我们需要自定义锁的话,也变得非常简单,只需要选择不同的模式实现不同的加锁和解锁的模板方法即可。


2、重入锁ReentrantLock与synchronized关键字的对比

2.1、synchronized关键字回顾

先回顾下synchronized的相关知识:【具体可点击:synchronized关键字详解

Java提供了内置锁来支持多线程的同步,JVM根据synchronized关键字来标识同步代码块,当线程进入同步代码块时会自动获取锁,退出同步代码块时会自动释放锁,一个线程获得锁后其他线程将会被阻塞。

每个Java对象都可以用做一个实现同步的锁,synchronized关键字可以用来修饰对象方法,静态方法和代码块,当修饰对象方法和静态方法时锁分别是方法所在的对象和Class对象,当修饰代码块时需提供额外的对象作为锁。每个Java对象之所以可以作为锁,是因为在对象头中关联了一个monitor对象(管程),线程进入同步代码块时会自动持有monitor对象,退出时会自动释放monitor对象,当monitor对象被持有时其他线程将会被阻塞。当然这些同步操作都由JVM底层帮你实现了,但以synchronized关键字修饰的方法和代码块在底层实现上还是有些区别的。

synchronized关键字修饰的方法是隐式同步的,即无需通过字节码指令来控制的,JVM可以根据方法表中的ACC_SYNCHRONIZED访问标志来区分一个方法是否是同步方法;

而synchronized关键字修饰的代码块是显式同步的,它是通过monitorentermonitorexit字节码指令来控制线程对管程的持有和释放。

monitor对象内部持有_count字段,_count等于0表示管程未被持有,_count大于0表示管程已被持有,每次持有线程重入时_count都会加1,每次持有线程退出时_count都会减1,这就是内置锁重入性的实现原理。另外,monitor对象内部还有两条队列_EntryList和_WaitSet,对应着AQS的同步队列和条件队列,当线程获取锁失败时会到_EntryList中阻塞,当调用锁对象的wait方法时线程将会进入_WaitSet中等待,这是内置锁的线程同步和条件等待的实现原理。

2.2、ReentrantLock和synchronized的比较

在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile。我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可见性。在大多数情况下,这些机制都能很好地完成工作,但却无法实现一些更高级的功能,例如,无法中断一个正在等待获取锁的线程,无法实现限定时间的获取锁机制,无法实现非阻塞结构的加锁规则等。而这些更灵活的加锁机制通常都能够提供更好的活跃性或性能。因此,在Java5.0中增加了一种新的机制:ReentrantLock。

ReentrantLock类实现了Lock接口,并提供了与synchronized相同的互斥性和内存可见性,它的底层是通过AQS来实现多线程同步的。与内置锁相比ReentrantLock不仅提供了更丰富的加锁机制,而且在性能上也不逊色于内置锁(在以前的版本中甚至优于内置锁)。

ReentrantLock和synchronized的主要区别如下:

1、synchronized关键字是Java提供的内置锁机制,其同步操作由底层JVM实现,而ReentrantLock是java.util.concurrent包提供的显式锁,其同步操作由AQS同步器提供支持。

2、ReentrantLock在加锁和内存上提供的语义与内置锁相同,此外它还提供了一些其他功能,包括定时的锁等待,可中断的锁等待,公平锁,以及实现非块结构的加锁。

3、事实上确实有许多人使用ReentrantLock来替代synchronized关键字的加锁操作。但是内置锁仍然有它特有的优势,内置锁为许多开发人员所熟悉,使用方式也更加的简洁紧凑,因为显式锁必须手动在finally块中调用unlock,所以使用内置锁相对来说会更加安全些。

4、同时未来更加可能会去提升synchronized而不是ReentrantLock的性能。因为synchronized是JVM的内置属性,它能执行一些优化,例如对线程封闭的锁对象的锁消除优化,通过增加锁的粒度来消除内置锁的同步,而如果通过基于类库的锁来实现这些功能,则可能性不大。

在JDK 1.6之后,虚拟机对于synchronized关键字进行整体优化后,在性能上synchronized与ReentrantLock已没有明显差距,因此在使用选择上,需要根据场景而定,大部分情况下我们依然建议是synchronized关键字,原因之一是使用方便语义清晰,二是性能上虚拟机已为我们自动优化。而ReentrantLock提供了多样化的同步特性,如超时获取锁、可以被中断获取锁(synchronized的同步是不能中断的)、等待唤醒机制的多个条件变量(Condition)等,因此当我们确实需要使用到这些功能是,可以选择ReentrantLock。


3、重入锁的一个简单案例

package zju.com.lock;

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo implements Runnable {

	public static ReentrantLock lock = new ReentrantLock();
	public static int count = 0;
	
	@Override
	public void run() {
		for(int j = 0; j < 10000000; j++){
			lock.lock();
			// 支持重入锁
			lock.lock();
			try {
				count++;   // 临界区:共享变量
			} finally {
				// 执行两次解锁
				lock.unlock();
				lock.unlock();				
			}
		}
	}

	public static void main(String[] args) throws InterruptedException {
		
		ReentrantLockDemo tld = new ReentrantLockDemo();
		Thread t1 = new Thread(tld);
		Thread t2 = new Thread(tld);
		
		t1.start();
		t2.start();
		
		t1.join();
		t2.join();
		
		// 产看count的结果
		System.out.println(count);  // 20000000
	}
}

案例代码非常简单,我们使用两个线程同时操作临界资源 i,执行自增操作,使用ReenterLock进行加锁,解决线程安全问题,这里进行了两次重复加锁。由于ReenterLock支持重入,因此这样是没有问题的,需要注意的是在finally代码块中,需执行两次解锁操作才能真正成功地让当前执行线程释放锁。


4、重入锁ReentrantLock的源码分析

4.1、获取锁和释放锁

先看下使用ReentrantLock加锁的示例代码。

public void doSomething() {
    // 默认是获取一个非公平锁
    ReentrantLock lock = new ReentrantLock();
    try{
        // 执行前先加锁
        lock.lock();   
        //执行操作...
    }finally{
        // 最后释放锁
        lock.unlock();
    }
}

以下是获取锁和释放锁这两个操作的API:

// 获取锁的操作
public void lock() {
    sync.lock();
}
// 释放锁的操作
public void unlock() {
    sync.release(1);
}

可以看到获取锁和释放锁的操作分别委托给Sync对象的lock方法和release方法。

public class ReentrantLock implements Lock, java.io.Serializable {

    private final Sync sync;

    abstract static class Sync extends AbstractQueuedSynchronizer {
        abstract void lock();
    }

    // 实现非公平锁的同步器
    static final class NonfairSync extends Sync {
        final void lock() {
            ...
        }
    }

    / /实现公平锁的同步器
    static final class FairSync extends Sync {
        final void lock() {
            ...
        }
    }
}

每个ReentrantLock对象都持有一个Sync类型的引用,这个Sync类是一个抽象内部类它继承自AbstractQueuedSynchronizer,它里面的lock方法是一个抽象方法。ReentrantLock的成员变量sync是在构造时赋值的,下面我们看看ReentrantLock的两个构造方法都做了些什么?

// 默认无参构造器
public ReentrantLock() {
    sync = new NonfairSync();
}

// 有参构造器
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

调用默认无参构造器会将NonfairSync实例赋值给sync,此时锁是非公平锁,即Reentrant默认是非公平锁。有参构造器允许通过参数来指定是将FairSync实例还是NonfairSync实例赋值给sync。

NonfairSync和FairSync都是继承自Sync类并重写了lock()方法,所以公平锁和非公平锁在获取锁的方式上有些区别,这个我们下面会讲到。

再来看看释放锁的操作,每次调用unlock()方法都只是去执行sync.release(1)操作,这步操作会调用AbstractQueuedSynchronizer类的release()方法,我们再来回顾一下。

// 释放锁的操作(独占模式)
public final boolean release(int arg) {
    // 拨动密码锁, 看看是否能够开锁
    if (tryRelease(arg)) {
        // 获取head节点
        Node h = head;
        // 如果head结点不为空并且等待状态不等于0就去唤醒后继节点
        if (h != null && h.waitStatus != 0) {
            // 唤醒后继节点
            unparkSuccessor(h);
        }
        return true;
    }
    return false;
}

这个release()方法是AQS提供的释放锁操作的API,它首先会去调用tryRelease()方法去尝试获取锁,tryRelease()方法是抽象方法,它的实现逻辑在子类Sync里面。

abstract static class Sync extends AbstractQueuedSynchronizer {

    ...

    // 尝试释放锁
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        // 如果持有锁的线程不是当前线程就抛出异常
        if (Thread.currentThread() != getExclusiveOwnerThread()) {
            throw new IllegalMonitorStateException();
        }
        boolean free = false;
        // 如果同步状态为0则表明锁被释放
        if (c == 0) {
            // 设置锁被释放的标志为真
            free = true;
            // 设置占用线程为空
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }

    ...
}

这个tryRelease()方法首先会获取当前同步状态,并将当前同步状态减去传入的参数值得到新的同步状态,然后判断新的同步状态是否等于0,如果等于0则表明当前锁被释放,然后先将锁的释放状态置为真,再将当前占有锁的线程清空,最后调用setState()方法设置新的同步状态并返回锁的释放状态。

4.2、公平锁和非公平锁

我们知道ReentrantLock是公平锁还是非公平锁是基于sync指向的是哪个具体实例而决定的。

在ReentrantLock的构造函数中会为成员变量sync赋值:

// 默认无参构造器
public ReentrantLock() {
    sync = new NonfairSync();
}

// 有参构造器
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

如果赋值为NonfairSync实例则表明是非公平锁,如果赋值为FairSync实例则表明为公平锁。如果是公平锁,线程将按照它们发出请求的顺序来获得锁,但在非公平锁上,则允许插队行为:当一个线程请求非公平的锁时,如果在发出请求的同时该锁的状态变为可用,那么这个线程将跳过队列中所有等待的线程直接获得这个锁。

下面我们先看看非公平锁的获取方式

// 非公平锁的获取
static final class NonfairSync extends Sync {
    // 实现父类的抽象获取锁的方法
    final void lock() {
        // 使用CAS方式设置同步状态
        if (compareAndSetState(0, 1)) {
            // 如果设置成功则表明锁没被占用
            setExclusiveOwnerThread(Thread.currentThread());
        } else {
            // 否则表明锁已经被占用, 调用acquire让线程去同步队列排队获取
            // acquire(1)是AQS类中的方法
            acquire(1);
        }
    }
    // 尝试获取锁的方法
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

// 调用的是AQS中的方法:以不可中断模式获取锁(独占模式)
public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
        selfInterrupt();
    }
}

可以看到在非公平锁的 lock方法中,线程第一步就会以CAS方式将同步状态的值从0改为1。其实这步操作就等于去尝试获取锁,如果更改成功则表明线程刚来就获取了锁,而不必再去同步队列里面排队了。如果更改失败则表明线程刚来时锁还未被释放,所以接下来就调用AQS类中的acquire()方法。

我们知道这个acquire方法是继承自AbstractQueuedSynchronizer的方法,现在再来回顾一下该方法,线程进入acquire方法后首先去调用tryAcquire方法尝试去获取锁,由于NonfairSync覆盖了tryAcquire方法,并在方法中调用了父类Sync的nonfairTryAcquire方法,所以这里会调用到nonfairTryAcquire方法去尝试获取锁。

我们看看这个nonfairTryAcquire()方法具体做了些什么:

abstract static class Sync extends AbstractQueuedSynchronizer {

    ...

    // 非公平的获取锁
    final boolean nonfairTryAcquire(int acquires) {
        // 获取当前线程
        final Thread current = Thread.currentThread();
        // 获取当前同步状态
        int c = getState();
        // 如果同步状态为0则表明锁没有被占用
        if (c == 0) {
            // 使用CAS更新同步状态
            if (compareAndSetState(0, acquires)) {
                // 设置目前占用锁的线程
                setExclusiveOwnerThread(current);
                return true;
            }
        // 否则的话就判断持有锁的是否是当前线程
        }else if (current == getExclusiveOwnerThread()) {
            // 如果锁是被当前线程持有的, 就直接修改当前同步状态
            int nextc = c + acquires;
            if (nextc < 0) {
                throw new Error("Maximum lock count exceeded");
            }
            setState(nextc);
            return true;
        }
        // 如果持有锁的不是当前线程则返回失败标志
        return false;
    }

    ...
}

nonfairTryAcquire()方法是Sync的方法,我们可以看到线程进入此方法后首先去获取同步状态,如果同步状态为0就使用CAS操作更改同步状态,其实这又是获取了一遍锁。如果同步状态不为0表明锁被占用,此时会先去判断持有锁的线程是否是当前线程,如果是的话就将同步状态加1,否则的话这次尝试获取锁的操作宣告失败。于是会调用addWaiter方法将线程添加到同步队列。

综上来看,在非公平锁的模式下一个线程在进入同步队列之前会尝试获取两遍锁,如果获取成功则不进入同步队列排队,否则才进入同步队列排队。

接下来我们看看公平锁的获取方式

// 实现公平锁的同步器
static final class FairSync extends Sync {
    // 实现父类的抽象获取锁的方法
    final void lock() {
        // 调用acquire让线程去同步队列排队获取
        acquire(1);
    }
    // 尝试获取锁的方法
    protected final boolean tryAcquire(int acquires) {
        // 获取当前线程
        final Thread current = Thread.currentThread();
        // 获取当前同步状态
        int c = getState();
        // 如果同步状态0则表示锁没被占用
        if (c == 0) {
            // 判断同步队列是否有前驱节点
            if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
                // 如果没有前驱节点且设置同步状态成功就表示获取锁成功
                setExclusiveOwnerThread(current);
                return true;
            }
        // 否则判断是否是当前线程持有锁
        }else if (current == getExclusiveOwnerThread()) {
            // 如果是当前线程持有锁就直接修改同步状态
            int nextc = c + acquires;
            if (nextc < 0) {
                throw new Error("Maximum lock count exceeded");
            }
            setState(nextc);
            return true;
        }
        // 如果不是当前线程持有锁则获取失败
        return false;
    }
}

调用公平锁的lock方法时会直接调用acquire方法。同样的,acquire方法首先会调用FairSync重写的tryAcquire方法来尝试获取锁。在该方法中也是首先获取同步状态的值,如果同步状态为0则表明此时锁刚好被释放,这时和非公平锁不同的是它会先去调用 hasQueuedPredecessors() 方法查询同步队列中是否有人在排队,如果没人在排队才会去修改同步状态的值,可以看到公平锁在这里采取礼让的方式而不是自己马上去获取锁

除了这一步和非公平锁不一样之外,其他的操作都是一样的。综上所述,可以看到公平锁在进入同步队列之前只检查了一遍锁的状态,即使是发现了锁是开的也不会自己马上去获取,而是先让同步队列中的线程先获取,所以可以保证在公平锁下所有线程获取锁的顺序都是先来后到的,这也保证了获取锁的公平性。

我们为什么不希望所有锁都是公平的呢?

毕竟公平是一种好的行为,而不公平是一种不好的行为。由于线程的挂起和唤醒操作存在较大的开销而影响系统性能,特别是在竞争激烈的情况下公平锁将导致线程频繁的挂起和唤醒操作,而非公平锁可以减少这样的操作,所以在性能上将会优于公平锁。

另外,由于大部分线程使用锁的时间都是非常短暂的,而线程的唤醒操作会存在延时情况,有可能在A线程被唤醒期间B线程马上获取了锁并使用完释放了锁,这就导致了双赢的局面,A线程获取锁的时刻并没有推迟,但B线程提前使用了锁,并且吞吐量也获得了提高。


5、等待队列的实现机制

内置的等待队列存在一些缺陷,每个内置锁都只能有一个相关联的等待队列,这导致多个线程可能在同一个等待队列上等待不同的条件,那么每次调用notifyAll时都会将所有等待的线程唤醒,当线程醒来后发现并不是自己等待的条件,转而又会被挂起。这导致做了很多无用的线程唤醒和挂起操作,而这些操作将会大量浪费系统资源,降低系统的性能。

如果想编写一个带有多个条件的并发对象,或者想获得除了等待队列可见性之外的更多控制权,就需要使用显式的Lock和Condition而不是内置锁和等待队列。

一个Condition和一个Lock关联在一起,就像一个等待队列和一个内置锁相关联一样。要创建一个Condition,可以在相关联的Lock上调用Lock.newCondition方法。我们先来看一个使用Condition的示例。

public class BoundedBuffer {

    final Lock lock = new ReentrantLock();
    final Condition notFull = lock.newCondition();   //条件谓词:notFull
    final Condition notEmpty = lock.newCondition();  //条件谓词:notEmpty
    final Object[] items = new Object[100];
    int putptr, takeptr, count;

    // 生产方法
    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length)
                notFull.await();  // 队列已满, 线程在notFull队列上等待
            items[putptr] = x;
            if (++putptr == items.length) putptr = 0;
            ++count;
            notEmpty.signal();    // 生产成功, 唤醒notEmpty队列的结点
        } finally {
            lock.unlock();
        }
    }

    // 消费方法
    public Object take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0)
                notEmpty.await();  // 队列为空, 线程在notEmpty队列上等待
            Object x = items[takeptr];
            if (++takeptr == items.length) takeptr = 0;
            --count;
            notFull.signal();     // 消费成功, 唤醒notFull队列的结点
            return x;
        } finally {
            lock.unlock();
        }
    } 
}

一个lock对象可以产生多个等待队列,上诉案例里产生了两个等待队列notFull和notEmpty。当容器已满时再调用put方法的线程需要进行阻塞,等待条件为真(容器不满)才醒来继续执行;当容器为空时再调用take方法的线程也需要阻塞,等待条件为真(容器不空)才醒来继续执行。

这两类线程是根据不同的条件进行等待的,所以它们会进入两个不同的条件队列中阻塞,等到合适时机再通过调用Condition对象上的API进行唤醒。下面是newCondition方法的实现代码。

// 创建等待队列
public Condition newCondition() {
    return sync.newCondition();
}

abstract static class Sync extends AbstractQueuedSynchronizer {
    
    ...    

    // 新建Condition对象
    final ConditionObject newCondition() {
        return new ConditionObject();
    }

    ...
}

ReentrantLock上的等待队列的实现都是基于AbstractQueuedSynchronizer的,我们在调用newCondition方法时所获得的Condition对象就是AQS的内部类ConditionObject的实例。所有对等待队列的操作都是通过调用ConditionObject对外提供的API来完成的。

有关于ConditionObject的具体实现大家可以查阅我的这篇文章:队列同步器AQS源码分析之Condition接口、等待队列


  • AQS系列文章:

1、队列同步器AQS源码分析之概要分析

2、队列同步器AQS源码分析之独占模式

3、队列同步器AQS源码分析之共享模式

4、队列同步器AQS源码分析之Condition接口、等待队列

猜你喜欢

转载自blog.csdn.net/pcwl1206/article/details/85009395