并发编程之Lock及其实现类

本篇介绍Lock接口、重入锁、读写锁和Condition等,部门内容总结摘抄自《Java并发编程的艺术》和《Java并发编程实战》,仅作笔记。

Lock

锁是用来控制多个线程方文共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源。在Lock接口出现之前,Java程序是靠synchronized关键字实现锁功能的,在JAVA SE5之后,并发包中新增了Lock接口以及相关实现类用来实现锁功能。它提供了与synchronized关键字类似的同步功能,只是在使用时需要显示的获取和释放锁。虽然它缺少了隐式获取释放锁的便捷性,但却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。

API

 Lock接口包含以下方法:

//获得锁
void lock();
//如果当前线程未被中断,则获取锁,可响应中断
void lockInterruptibly();
//获取一个绑定到此Lock实例的Condition对象
Condition newCondition();
//在调用时锁为空闲状态才获取锁,可响应中断
boolean tryLock();
//如果锁在给定的时间内空闲,且未被中断,则获取锁
boolean tryLock(long time, TimeUnit unit);
//释放锁
void unlock();

Lock的使用方式如下:

Lock lock = new Lock实现类();
lock.lock();
try{
}finally{
    lock.unlock();
}

在finally块中释放锁,目的是保证在获取到锁后,最终能被释放。

Lock接口提供的synchronized关键字所不具备的主要特性如下表。

特性描述
尝试非阻塞的获取锁当前线程尝试获取锁,如果这一时刻没有锁没有被其他线程获取到,则成功获取并持有锁
能被中断的获取锁与synchronized不同,获取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,锁也会被释放
超时获取锁在指定的截止时间之前获取锁,如果截止时间到了仍旧无法获取锁,则返回

重入锁ReentrantLock

重入锁ReentrantLock,实现了Lock接口,并且提供了与synchronized相同的互斥性和内存可见性。在获取ReentrantLock时,有着与进入同步代码块相同的内存语义,在释放ReentrantLock时,有着与退出同步代码块相同的内存语义。与synchronized一样,ReentrantLock还提供可重入的加锁语义。

ReentrantLock支持在Lock接口中定义的所有获取锁模式,并且与synchronized相比,它还支持获取锁时的公平和非公平性选择。如果在绝对时间上,先对锁进行获取的请求一定先被满足,那么这个锁是公平的,反之,是不公平的。公平的获取锁,也就是等待时间最常的线程最优先获取锁,也可以说锁获取时顺序地。

API

ReentrantLock的构造函数如下:

//默认构造函数
ReentrantLock();
//传入是否为公平锁的参数的构造函数
ReentrantLock(boolean fair);

常用方法如下:

//返回当前锁是否是公平锁
public final boolean isFair();
//返回此锁是否被任何线程持有
final boolean isLocked();
//获取锁,获取不到边一直等待
final boolean isLocked();
//获得与此lock对象绑定的Condition对象
public Condition newCondition();
//尝试获取锁,无论成功还是失败都直接返回
public boolean tryLock();
//在指定的时间内获取锁,超时返回
public boolean tryLock(long timeout, TimeUnit unit);
//释放锁
public void unlock();

公平性

在ReentrantLock的构造函数中提供了两种公平性选择:创建一个非公平的锁或者创建一个公平的锁。在公平的锁上,锁的获取顺序与请求的绝对时间顺序一致,如果有另一个线程持有这个锁或有其他线程在队列中等待这个锁,新发出请求的线程将被放入队列末尾。在非公平的锁上,如果一个线程发出请求的同时该锁的状态变为可用,那么这个线程将跳过队列中所有等待的线程并获得这个锁,只有当该锁被某个线程持有时,新发出请求的线程才会被放入队列。默认的ReentrantLock是非公平的。

在激烈竞争的情况下,非公平锁的性能高于公平锁的性能,其中一个原因在于:在恢复一个被挂起的线程与该线程真正开始运作之间存在着严重的延迟。假设线程A持有一个锁,并且线程B请求这个锁。由于这个锁已被线程A持有,因此B将被挂起。当A释放锁时,B将被唤醒,因此再次尝试获取锁。与此同时,C也请求这个锁,而C有可能在B被完全唤醒之前获得、使用以及释放这个锁,这样的情况是一种双赢的局面。

当持有锁的时间相对较长,或者请求锁的平均时间间隔较长,应该使用公平锁。在这些情况下,“插队”带来的吞吐量提升则可能不会出现。

实现分析

ReentrantLock类的功能基本上都是通过名为Sync的抽象静态内部类实现的,而Sync又继承于AbstractQueuedSynchronizer类,关于AbstractQueuedSynchronized类在这篇文章中介绍了,此处不再赘述。获取锁的具体实现则由继承了Sync类的NofairSync类和FairSync完成,这两个类分别对应非公平锁和公平锁的实现。

以上四个类的类图如下:

非公平锁获取锁的lock()方法的具体实现如下:

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

在以上代码中,先使用CAS判断当前锁是否被任何线程占有(即state是否为0),如果未占有,则将state设置为1,并且保存获取锁的线程;如果已有其他线程占有该锁(即state大于0),则调用AQS中的acquire()方法。这是一个模版方法,其基本逻辑是调用tryAcquire()方法(该方法由子类实现)尝试获取锁,如果获取失败则构造一个节点对象(包含线程信息)并加入同步队列的尾部。因此具体获取锁的代码还是由NonfairSync以及FairSync类实现。

非公平锁NonfairSync的tryAcquire()方法实际上是调用父类Sync的nonfairTryAcquire()方法,因此我们直接看这个方法,其代码如下:

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

在以上代码中,首先判断该锁是否未被任意线程获取(state是否为0),如果是,则使用CAS将state设置为指定的值然后保存获取锁的线程,返回true;否则判断当前线程是否为已经获取锁的线程,如果是,则更新state的值,然后返回true(这一过程使得ReentrantLock可重入)。否则返回false,表示锁获取失败。

公平锁的lock()方法如下:

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

与非公平锁不同,公平锁的lock()方法并没有立即尝试获取锁,而是直接调用acquire()方法。原因与他们的区别有关,非公平锁与请求时间顺序无关,而公平锁需要按照FIFO的顺序,因此这里不能直接获取锁。

公平锁的tryAcquire()方法代码如下:

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    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;
}

以上代码逻辑为:当该锁没有被任何线程获取时,首先判断是否有线程在等待获取,如果没有则使用CAS设置state的值并且保存获取锁的线程。当该锁已被任意线程获取的流程与非公平锁一致。

除了获取锁外,因为可重入的特性,Sync还实现了释放锁tryRelease()方法,其代码如下:

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

首先判断当前线程是否是获取锁的线程,如果不是则抛出IllegalMonitorStateException。然后判断同步状态state是否为0,如果为0则表示线程释放了该锁,将保存的获取锁的线程置为null。最后更新state的值。

选择synchronized还是ReentrantLock?

ReentrantLock在加锁和内存上提供的语义与内置锁相同,此外它还提供了一些其他功能,包括定时的锁等待、可中断的锁等待、公平性以及实现非块结构的加锁。如此看来应该使用ReentrantLock代替内置锁,但事实并非如此。

内置锁与显式锁相比,还是具有很大的优势。内置锁为许多开发人员所熟悉,并且简介紧凑。在许多现有的程序中已经使用了内置锁,如果将这两种机制混合使用,不仅容易令人困惑,也容易发生错误。ReentrantLock的危险性要比同步机制高,如果忘记在finally中调用unlock,虽然代码表面上可以正常运行,实际上很可能会伤及其他代码。仅当内置锁不能满足需求时,才考虑使用ReentrantLock。

读写锁

ReentrantLock实现了一种标准的互斥锁:每次最多只有一个线程能持有ReentrantLock。但对于维护数据的完整性来说,互斥通常是一种过于强硬的加锁规则,因此也就不必要的限制了并发性。互斥是一种保守的加锁策略,虽然可以避免“写/写”冲突和“写/读”冲突,但同样也避免了“读/读”冲突。许多情况下,数据机构上的访问操作都是读操作。此时,如果能够放宽加锁需求,允许多个执行读操作的线程同时访问数据结构,程序的性能就会得到提升。只要每个线程都能确保读取到最新的数据,并且在读数据时不会有其他线程修改数据,那就不会发生问题。一个资源可以被多个读操作访问,或者被一个写操作访问,但这两者不能同时进行。

在Java 5以前,如果需要完成上述工作需要使用Java的等待通知机制,当写操作开始时,所有晚于写操作的线程都会进入等待状态,只有写操作完成并进行通知后,所有等待的读操作才能继续执行,这样做的目的是使读操作可以读取到正确的数据,不会出现脏读。

一般情况下,读写锁的性能都比互斥锁好,因为大多数场景读是多于写的。

下面逐一介绍读写锁相关的API。

ReadWriteLock接口

ReadWriteLock接口只有两个方法,一个用于读操作,一个用于写操作。ReadWriteLock接口所有的实现类都必须保证一个线程获取读锁后可以看到之前发布的写锁的所有更新。

//获取读锁
Lock readLock();
//获取写锁
Lock writeLock();

ReentrantReadWriteLock类

ReentrantReadWriteLock类实现了ReadWriteLock接口,ReentrantReadWriteLock除了实现接口方法外,还提供了一些便于监控其内部工作状态的方法。

ReentrantReadWriteLock的两个构造函数实现如下:

//默认构造函数
public ReentrantReadWriteLock() {
    this(false);
}
//传入是否为公平锁的参数
public ReentrantReadWriteLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
    readerLock = new ReadLock(this);
    writerLock = new WriteLock(this);
}

其他常用方法如下:

//返回当前拥有写锁的线程,如果没有返回null
final Thread getOwner();
//返回正在等待获取读锁的线程集合
protected Collection<Thread> getQueuedReaderThreads();
//返回当前线程在此锁重入读锁的数量
final int getReadHoldCount();
//返回当前线程在此锁重入写锁的数量
getWriteHoldCount;
//返回当前线程是否为公平锁
public final boolean isFair();
//查询写锁是否由任何线程持有
public boolean isWriteLocked();
//查询写锁是否由当前线程持有
public boolean isWriteLockedByCurrentThread();
//返回读锁
public ReentrantReadWriteLock.ReadLock  readLock();
//返回写锁
public ReentrantReadWriteLock.WriteLock writeLock();

实现分析

在第二个构造函数中初始化了一个ReadLock对象和WriteLock对象,这两个对象都是ReentrantReadWriteLock的静态内部类,而这两个类又都依赖名为Sync的一个抽象静态内部类,这里与ReentrantLock类似,Sync同样继承了AQS。这几个类的类图如下:

读锁ReadLock的lock()方法调用的是AQS中的获取共享锁的方法acquireShared(),unlock()方法调用的是AQS释放共享锁的方法releaseShared()。写锁WriteLock的lock()方法调用的是AQS中获取独占锁的方法acquire(),unlock()方法调用的是AQS释放独占锁的方法release()。与ReentrantLock一样,真正实现获取锁的是sync中的tryAcquire()和tryAcquireShared(),释放锁的是tryRelease()和tryReleaseShared()。原因在上面介绍ReenrantLock类时介绍过了,此处不再赘述。因此我们只讨论这四个方法。

在接上读写锁的获取与释放之前我们还需要介绍一些读写状态的设计。在ReentrantLock中自定义同步器的同步状态表示锁被一个线程重复获取的次数,而读写锁的自定义同步器需要在同步状态上维护一个写线程和多个读线程的状态。

在一个整型变量上维护多种状态,就需要使用“按位切割”。读写锁将变量切分成了两个部分,高16位表示读,低16位表示写,划分方式如下图:

当前同步状态表示一个线程已经获取了写锁,且重入了两次,同时也连续获取了两次读锁。假设当前同步状态值为S,写状态等于S&0x0000FFFF(将高16位全部抹去),读状态等于S>>>16(无符号补0右移16位)。当写状态增加1时,等于S+1,当读状态增加1时,等于S+(1<<<16),也就是S+0x00010000。ReentrantReadWriteLock中计算读写锁都是类似这样运算的。

根据状态的划分能得出一个结论:S不等于0时,当写状态(S&0x0000FFFF)等于0时,则读状态(S>>>16)大于0,即读锁已被获取。

写锁是一个支持重进入的互斥锁。如果当前线程已经获取了写锁,则增加写状态。如果当前线程在获取写锁时,读锁已被获取(读状态不为0)或者该线程不是已经获取写锁的线程,则当前线程进入等待状态。

获取写锁的tryAcquire()方法代码如下:

protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c);
    if (c != 0) {
        //如果同步状态不为0且写状态为0,则读状态不为0,即读锁已被获取
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        setState(c + acquires);
        return true;
    }
    if (writerShouldBlock() || !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

首先获取写状态w,判断同步状态c不为0时,写状态w为0或者当前线程并非获取写锁的线程,则返回false。然后判断写锁重入次数是否达到上限,更新同步状态等。

在以上代码的逻辑中,如果存在读锁则写锁不能被获取,原因在于:读写锁要确保写锁的操作对读锁可见,如果允许读锁在已被获取的情况下对写锁的获取,那么正在运行的其他线程就无法感知到当前写线程的操作。因此,只有等其他读线程都释放了读锁,写锁才能被当前线程获取,而写锁一旦被获取,则其他读写线程的后续访问均被阻塞。

写锁的释放与ReentrantLock的释放过程基本类似,每次释放均减少写状态,当写状态为0时表示写锁已被释放,从而等待的读写线程能够继续访问读写锁,同时前次写线程的修改对后续读写线程可见。

读锁是一个支持重进入的共享锁,它能够被多个线程同时获取,在没有其他写线程访问时,读锁总是能被成功获取,所做的也只是增加读状态。如果当前线程已经获取了读锁,则增加读状态。如果当前线程在获取读锁时,写锁已被其他线程获取,则进入等待状态。读状态是所有线程获取读锁次数的综合,每个线程各自获取读锁的次数只能选择保存在ThreadLocal中,由线程自身维护,这使得获取读锁的代码变得复杂。

获取读锁的tryAcquireShared()方法的代码如下:

protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
        return -1;
    int r = sharedCount(c);
    if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) {
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

首先判断是否有其他线程获取了写锁,如果是,则当前线程获取读锁失败,进入等待状态。否则当前线程增加读状态,成功获取读锁。读锁的每次释放均减少读状态,减少的值是(1<<16)。

锁降级

锁降级是指当前线程拥有写锁,再获取到读锁,随后释放写锁的过程。锁降级主要是为了保证数据的可见性,如果当前线程不获取读锁而是直接释放写锁,假设此刻另一个线程获取了写锁并修改了数据,那么当前线程无法感知到另一个线程的数据更新。如果当前线程获取读锁,即遵循锁降级的步骤,则线程T将会被阻塞,直到当前线程使用数据并释放读锁之后,线程T才能获取写锁并进行数据更新。

笔者看到书中上面这一段内容怀疑了一下,因为看似好像不使用锁降级也是一样的,因为锁的存在就保证了数据的可见性。看到网上很多大牛的理解,比较靠谱的大概如下:

锁降级在一个读写操作都有的场景中,线程A执行完写操作后释放了写锁,线程B获取写锁并修改了数据,随后线程A获取读锁然后执行后续操作。由于数据可见性,此时线程A确实读到了线程B的执行结果,但线程A的场景可能需要使用本身写操作的结果,这时程序可能就会出现意外。如果使用锁降级就不存在这种情况,线程A获取写锁,获取读锁,释放写锁,这时没有其他线程可以获取写锁修改数据。

Condition

任意一个Java对象都拥有一组监视器方法,主要包括wait()、wait(long timeout)、notify()以及notifyAll()方法,这些方法与synchronized同步关键字配合,可以实现等待/通知模式。Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式,但这两者在使用方式以及功能特性上有一些差别,如下图:

Object的监视器方法与Condition接口对比
对比项Object Monitor MethodsCondition
前置条件获取对象的锁

调用Lock.lock()获取锁

调用Lock.newCondition()获取Condition对象

调用方式直接调用,如object.wait()直接调用,如condition.await()
等待队列个数一个多个
当前线程释放锁并进入等待状态支持支持
当前线程释放锁并进入等待状态,在等待状态中不响应中断不支持支持
当前线程释放锁并进入超时等待状态支持支持
当前线程释放锁并进入等待状态到将来的某个时间不支持支持
唤醒等待队列中的一个线程支持支持
唤醒等待队列中的全部线程支持支持

Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到Condition对象关联的锁。Condition对象是由Lock对象创建出来的,因此依赖Lock对象。

Condition的方法如下:

//当前线程进入等待状态直到被通知或中断
void await();
//当前线程进入等待状态直到被通知,对中断不敏感
void awaitUninterruptibly();
//当前线程进入等待状态直到被通知、中断或超时。返回值表示剩余时间,如果返回值为0或负数,则已超时
long awaitNanos(long nanosTimeout);
//当前线程进入等待状态直到被通知、中断或到某个时间。
//如果没有到指定时间就被通知,方法返回true,否则返回false
boolean awaitUntil(Date deadline);
//唤醒一个等待在Condition上的线程,该线程从等待方法返回前必须获得与Condition相关联的锁
void signal();
//唤醒所有等待在Condition上的线程,能够从等待方法返回的线程必须获得与Condition相关联的锁
void signalAll();

获取一个Condition必须通过Lock的newCondition()方法,多次调用则可以获取多个Condition对象,因此一个Lock可以有多条等待队列。下面通过一个有界队列的示例来深入了解Condition的使用方式,有界队列是一种特殊的队列,当队列为空时,队列的获取操作将会阻塞获取线程,直到队列中有新增元素,当队列已满时,队列的插入操作将会阻塞插入线程,知道队列出现“空位”。代码如下所示:

public class ConditionBoundedQueue {
    private int[] items = new int[5];
    private int count,addIndex,removeIndex;
    private Lock lock = new ReentrantLock();
    private Condition notEmpty =lock.newCondition();
    private Condition notFull = lock.newCondition();

    public static void main(String[] args) {
        ConditionBoundedQueue boundedQueue = new ConditionBoundedQueue();
        for (int i=0;i<10;i++){
            new Producer(boundedQueue).start();
        }
        for (int i=0;i<10;i++){
            new Consumer(boundedQueue).start();
        }
    }

    public static class Producer extends Thread{
        private ConditionBoundedQueue boundedQueue;
        Producer(ConditionBoundedQueue boundedQueue){
            this.boundedQueue = boundedQueue;
        }

        @Override
        public void run() {
            boundedQueue.add(new Random().nextInt(100));
        }
    }

    public static class Consumer extends Thread{
        private ConditionBoundedQueue boundedQueue;
        Consumer(ConditionBoundedQueue boundedQueue){
            this.boundedQueue = boundedQueue;
        }

        @Override
        public void run() {
            boundedQueue.remove();
        }
    }

    public void add(int item){
        lock.lock();
        try {
            while (count == items.length) {
                notFull.await();
            }
            items[addIndex] = item;
            count++;
            if (++addIndex == items.length) {
                addIndex = 0;
            }
            System.out.println("增加一个值后数组为:" + Arrays.toString(items));
            notEmpty.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void remove(){
        lock.lock();
        try {
            while (count == 0) {
                notEmpty.await();
            }
            count--;
            items[removeIndex] = 0;
            if (++removeIndex == items.length) {
                removeIndex = 0;
            }
            System.out.println("删除一个值后数组为:" + Arrays.toString(items));
            notFull.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

输出结果为:

增加一个值后数组为:[92, 0, 0, 0, 0]
增加一个值后数组为:[92, 56, 0, 0, 0]
增加一个值后数组为:[92, 56, 26, 0, 0]
增加一个值后数组为:[92, 56, 26, 25, 0]
增加一个值后数组为:[92, 56, 26, 25, 79]
删除一个值后数组为:[0, 56, 26, 25, 79]
增加一个值后数组为:[48, 56, 26, 25, 79]
删除一个值后数组为:[48, 0, 26, 25, 79]
增加一个值后数组为:[48, 75, 26, 25, 79]
删除一个值后数组为:[48, 75, 0, 25, 79]
删除一个值后数组为:[48, 75, 0, 0, 79]
删除一个值后数组为:[48, 75, 0, 0, 0]
增加一个值后数组为:[48, 75, 45, 0, 0]
删除一个值后数组为:[0, 75, 45, 0, 0]
删除一个值后数组为:[0, 0, 45, 0, 0]
增加一个值后数组为:[0, 0, 45, 17, 0]
删除一个值后数组为:[0, 0, 0, 17, 0]
删除一个值后数组为:[0, 0, 0, 0, 0]
增加一个值后数组为:[0, 0, 0, 0, 52]
删除一个值后数组为:[0, 0, 0, 0, 0]

实现分析

Condition的实现类是ConditionObject,ConditionObject是同步器AQS的内部类,因为Condition的操作需要获取相关的锁,所以作为同步器的内部类也较为合理。每个Condition对象都包含一个等待队列,该队列是Condition对象实现等待/通知功能的关键。

等待队列是一个FIFO的队列,在队列的每个节点都包含了一个线程引用,该线程就是在Condition对象上等待的线程,如果一个线程调用了Condition.await()方法,那么该线程就会释放锁、构造节点加入等待队列并进入等待状态。如果了解AQS就会发现这里好像与加入同步队列类似,事实上等待队列的节点类就是AQS的节点类Node。

一个Condition包含一个等待队列,之前提到过,一个Lock每调用一次newCondition()方法就有多个Condition。因此一个Lock拥有多个等待队列,但只有一个同步队列。

当线程调用await()方法时,当前线程(同步队列的首节点)需要做的事情如下:构造节点,加入等待队列中,释放同步状态,唤醒同步队列中的后继节点,进入等待状态。

调用Condition的signal()方法,将会唤醒在等待队列中等待时间最长的节点(首节点),在唤醒节点之前,会将节点移到同步队列中。调用该方法的前置条件是当前线程必须获取了锁,然后获取等待队列的首节点,将其移动到同步队列并使用LockSupport唤醒节点中的线程。

被唤醒后的节点,将从await()方法中的while循环中退出,进而调用同步器的acquireQueued()方法加入到获取同步状态的竞争中。成功获取同步状态后,被唤醒的线程将从先前调用的await()方法返回,此时该线程已经成功获取了锁。Condition的sigalAll()方法,相当于对等待队列中的每个节点都执行一个signal()方法,效果就是将等待队列中的所有节点全部移动到同步队列中,并唤醒每个节点的线程。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值