Java并发编程实战 构建自定义的同步工具总结

状态依赖性的管理
在单线程程序中调用一个方法时 如果某个基于状态的前提条件未得到满足(例如 连接池必须非空) 那么这个条件将永远无法成真 因此 在编写顺序程序中的类时 要使得这些类在它们的前提条件未被满足时就失败 但在并发程序中 基于状态的条件可能会由于其他线程的操作而改变:一个资源池可能在几条指令之前还是空的 但现在却变为非空的 因为另一个线程可能会返回一个元素到资源池 对于并发对象上依赖状态的方法 虽然有时候在前提条件不满足的情况下不会失败 但通常有一种更好的选择 即等待前提条件变为真
依赖状态的操作可以一直阻塞直到可以继续执行 这比使它们先失败再实现起来要更为方便且更不易出错 内置的条件队列可以使线程一直阻塞 直到对象进入某个进程可以继续执行的状态 并且当被阻塞的线程可以执行时再唤醒它们

可阻塞的状态依赖操作的结构

acquire lock on object state
while (precondition does not hold) {
	release lock
	wait until precondition might hold
	optionally fail if interrupted or timeout expires
	reacquire lock
}
perform action
	release lock

在生产者-消费者的设计中经常会使用像ArrayBlockingQueue这样的有界缓存 在有界缓存提供的put和take操作中都包含有一个前提条件:不能从空缓存中获取元素 也不能将元素放入已满的缓存中 当前提条件未满足时 依赖状态的操作可以抛出一个异常或返回一个错误状态(使其成为调用者的一个问题) 也可以保持阻塞直到对象进入正确的状态

有界缓存实现的基类

@ThreadSafe
public abstract class BaseBoundedBuffer <V> {
    @GuardedBy("this") private final V[] buf;
    @GuardedBy("this") private int tail;
    @GuardedBy("this") private int head;
    @GuardedBy("this") private int count;

    protected BaseBoundedBuffer(int capacity) {
        this.buf = (V[]) new Object[capacity];
    }

    protected synchronized final void doPut(V v) {
        buf[tail] = v;
        if (++tail == buf.length)
            tail = 0;
        ++count;
    }

    protected synchronized final V doTake() {
        V v = buf[head];
        buf[head] = null;
        if (++head == buf.length)
            head = 0;
        --count;
        return v;
    }

    public synchronized final boolean isFull() {
        return count == buf.length;
    }

    public synchronized final boolean isEmpty() {
        return count == 0;
    }
}

示例:将前提条件的失败传递给调用者

当不满足前提条件时 有界缓存不会执行相应的操作

@ThreadSafe
        public class GrumpyBoundedBuffer <V> extends BaseBoundedBuffer<V> {
    public GrumpyBoundedBuffer() {
        this(100);
    }

    public GrumpyBoundedBuffer(int size) {
        super(size);
    }

    public synchronized void put(V v) throws BufferFullException {
        if (isFull())
            throw new BufferFullException();
        doPut(v);
    }

    public synchronized V take() throws BufferEmptyException {
        if (isEmpty())
            throw new BufferEmptyException();
        return doTake();
    }
}

尽管这种方法实现起来很简单 但使用起来却并非如此 异常应该用于发生异常条件的情况中 缓存已满 并不是有界缓存的一个异常条件 就像 红灯 并不表示交通信号灯出现了异常 在实现缓存时得到的简化(使调用者管理状态依赖性)并不能抵消在使用时存在的复杂性 因为现在调用者必须做好捕获异常的准备 并且在每次缓存操作时都需要重试

调用GrumpyBoundedBuffer的代码

class ExampleUsage {
    private GrumpyBoundedBuffer<String> buffer;
    int SLEEP_GRANULARITY = 50;

    void useBuffer() throws InterruptedException {
        while (true) {
            try {
                String item = buffer.take();
                // use item
                break;
            } catch (BufferEmptyException e) {
                Thread.sleep(SLEEP_GRANULARITY);
            }
        }
    }
}

class BufferFullException extends RuntimeException {
}

class BufferEmptyException extends RuntimeException {
}

这种方法的一种变化形式是 当缓存处于某种错误的状态时返回一个错误值 这是一种改进 因为并没有放弃异常机制 抛出的异常意味着 对不起 请再试一次 但这种方法并没有解决根本问题:调用者必须自行处理前提条件失败的情况

示例:通过轮询与休眠来实现简单的阻塞

使用简单阻塞实现的有界缓存

@ThreadSafe
        public class SleepyBoundedBuffer <V> extends BaseBoundedBuffer<V> {
    int SLEEP_GRANULARITY = 60;

    public SleepyBoundedBuffer() {
        this(100);
    }

    public SleepyBoundedBuffer(int size) {
        super(size);
    }

    public void put(V v) throws InterruptedException {
        while (true) {
            synchronized (this) {
                if (!isFull()) {
                    doPut(v);
                    return;
                }
            }
            Thread.sleep(SLEEP_GRANULARITY);
        }
    }

    public V take() throws InterruptedException {
        while (true) {
            synchronized (this) {
                if (!isEmpty())
                    return doTake();
            }
            Thread.sleep(SLEEP_GRANULARITY);
        }
    }
}

SleepyBoundedBuffer的实现远比之前的实现复杂 缓存代码必须在持有缓存锁的时候才能测试相应的状态条件 因为表示状态条件的变量是由缓存锁保护的 如果测试失败 那么当前执行的线程将首先释放锁并休眠一段时间 从而使其他线程能够访问缓存 当线程醒来时 它将重新请求锁并再次尝试执行操作 因而线程将反复地在休眠以及测试状态条件等过程之间进行切换 直到可以执行操作为止
从调用者的角度看 这种方法能很好地运行 如果某个操作可以执行 那么就立即执行 否则就阻塞 调用者无须处理失败和重试 要选择合适的休眠时间间隔 就需要在响应性与CPU使用率之间进行权衡 休眠的间隔越小 响应性就越高 但消耗的CPU资源也越高

条件队列
条件队列 这个名字来源于:它使得一组线程(称之为等待线程集合)能够通过某种方式来等待特定的条件变成真 传统队列的元素是一个个数据 而与之不同的是 条件队列中的元素是一个个正在等待相关条件的线程
正如每个Java对象都可以作为一个锁 每个对象同样可以作为一个条件队列 并且Object中的wait notify和notifyAll方法就构成了内部条件队列的API 对象的内置锁与其内部条件队列是相互关联的 要调用对象X中条件队列的任何一个方法 必须持有对象X上的锁 这是因为 等待由状态构成的条件 与 维护状态一致性 这两种机制必须被紧密地绑定在一起:只有能对状态进行检查时 才能在某个条件上等待 并且只有能修改状态时 才能从条件等待中释放另一个线程
Object.wait会自动释放锁 并请求操作系统挂起当前线程 从而使其他线程能够获得这个锁并修改对象的状态 当被挂起的线程醒来时 它将在返回之前重新获取锁 从直观上来理解 调用wait意味着 我要去休息了 但当发生特定的事情时唤醒我 而调用通知方法就意味着 特定的事情发生了

使用条件队列实现的有界缓存

@ThreadSafe
        public class BoundedBuffer <V> extends BaseBoundedBuffer<V> {
    // CONDITION PREDICATE: not-full (!isFull())
    // CONDITION PREDICATE: not-empty (!isEmpty())
    public BoundedBuffer() {
        this(100);
    }

    public BoundedBuffer(int size) {
        super(size);
    }

    // BLOCKS-UNTIL: not-full
    public synchronized void put(V v) throws InterruptedException {
        while (isFull())
            wait();
        doPut(v);
        notifyAll();
    }

    // BLOCKS-UNTIL: not-empty
    public synchronized V take() throws InterruptedException {
        while (isEmpty())
            wait();
        V v = doTake();
        notifyAll();
        return v;
    }

    // BLOCKS-UNTIL: not-full
    // Alternate form of put() using conditional notification
    public synchronized void alternatePut(V v) throws InterruptedException {
        while (isFull())
            wait();
        boolean wasEmpty = isEmpty();
        doPut(v);
        if (wasEmpty)
            notifyAll();
    }
}

最终 BoundedBuffer变得足够好了 不仅简单易用 而且实现了明晰的状态依赖性管理 在产品的正式版本中还应包括限时版本的put和take 这样当阻塞操作不能在预计时间内完成时 可以因超时而返回 通过使用定时版本的Object.wait 可以很容易实现这些方法

使用条件队列
条件队列使构建高效以及高可响应性的状态依赖类变得更容易

条件谓词
条件谓词是使某个操作成为状态依赖操作的前提条件 在有界缓存中 只有当缓存不为空时 take方法才能执行 否则必须等待 对take方法来说 它的条件谓词就是 缓存不为空 take方法在执行之前必须首先测试该条件谓词 同样 put方法的条件谓词是 缓存不满 条件谓词是由类中各个状态变量构成的表达式 BaseBoundedBuffer在测试 缓存不为空 时将把count与0进行比较 在测试 缓存不满 时将把count与缓存的大小进行比较

将与条件队列相关联的条件谓词以及在这些条件谓词上等待的操作都写入文档

在条件等待中存在一种重要的三元关系 包括加锁 wait方法和一个条件谓词 在条件谓词中包含多个状态变量 而状态变量由一个锁来保护 因此在测试条件谓词之前必须先持有这个锁 锁对象与条件队列对象(即调用wait和notify等方法所在的对象)必须是同一个对象

每一次wait调用都会隐式地与特定的条件谓词关联起来 当调用某个特定条件谓词的wait时 调用者必须已经持有与条件队列相关的锁 并且这个锁必须保护着构成条件谓词的状态变量

过早唤醒
状态依赖方法的标准形式

void stateDependentMethod() throws InterruptedException {
	synchronized(lock) {
		while(!conditionPredicate())
			lock.wait();
}
}

当使用条件等待时(例如Object.wait或Condition.wait):

  • 通常都有一个条件谓词——包括一些对象状态的测试 线程在执行前必须首先通过这些测试
  • 在调用wait之前测试条件谓词 并且从wait中返回时再次进行测试
  • 在一个循环中调用wait
  • 确保使用与条件队列相关的锁来保护构成条件谓词的各个状态变量
  • 当调用wait notify或notifyAll等方法时 一定要持有与条件队列相关的锁
  • 在检查条件谓词之后以及开始执行相应的操作之前 不要释放锁

丢失的信号
丢失的信号是指:线程必须等待一个已经为真的条件 但在开始等待之前没有检查条件谓词 现在 线程将等待一个已经发过的事件

通知
在有界缓存中 如果缓存为空 那么在调用take时将阻塞 在缓存变为非空时 为了使take解除阻塞 必须确保在每条使缓存变为非空的代码路径中都发出一个通知 在BoundedBuffer中 只有一条代码路径 即在put方法之后 因此 put在成功地将一个元素添加到缓存后 将调用notifyAll 同样 take在移除一个元素后也将调用notifyAll 向任何正在等待 不为满 条件的线程发出通知:缓存已经不满了

每当在等待一个条件时 一定要确保在条件谓词变为真时通过某种方式发出通知

只有同时满足以下两个条件时 才能用单一的notify而不是notifyAll:
所有等待线程的类型都相同 只有一个条件谓词与条件队列相关 并且每个线程在从wait返回后将执行相同的操作
单进单出 在条件变量上的每次通知 最多只能唤醒一个线程来执行

在BoundedBuffer的put和take方法中采用的通知机制是保守的:每当将一个对象放入缓存或者从缓存中移走一个对象时 就执行一次通知 我们可以对其进行优化:首先 仅当缓存从空变为非空 或者从满转为非满时 才需要释放一个线程 并且 仅当put或take影响到这些状态转换时 才发出通知 这也被称为 条件通知 虽然 条件通知 可以提升性能 但却很难正确地实现(而且还会使子类的实现变得复杂) 因此在使用时应该谨慎

在BoundedBuffer.put中使用条件通知

@ThreadSafe
        public class BoundedBuffer <V> extends BaseBoundedBuffer<V> {
    // CONDITION PREDICATE: not-full (!isFull())
    // CONDITION PREDICATE: not-empty (!isEmpty())
    public BoundedBuffer() {
        this(100);
    }

    public BoundedBuffer(int size) {
        super(size);
    }

    // BLOCKS-UNTIL: not-full
    public synchronized void put(V v) throws InterruptedException {
        while (isFull())
            wait();
        doPut(v);
        notifyAll();
    }

    // BLOCKS-UNTIL: not-empty
    public synchronized V take() throws InterruptedException {
        while (isEmpty())
            wait();
        V v = doTake();
        notifyAll();
        return v;
    }

    // BLOCKS-UNTIL: not-full
    // Alternate form of put() using conditional notification
    public synchronized void alternatePut(V v) throws InterruptedException {
        while (isFull())
            wait();
        boolean wasEmpty = isEmpty();
        doPut(v);
        if (wasEmpty)
            notifyAll();
    }
}

示例:阀门类
闭锁能阻止线程通过开始阀门 并直到阀门被打开 此时所有的线程都可以通过该阀门 虽然闭锁机制通常都能满足需求 但在某些情况下存在一个缺陷:按照这种方式构造的阀门在打开后无法重新关闭

使用wait和notifyAll来实现可重新关闭的阀门

@ThreadSafe
public class ThreadGate {
    // CONDITION-PREDICATE: opened-since(n) (isOpen || generation>n)
    @GuardedBy("this") private boolean isOpen;
    @GuardedBy("this") private int generation;

    public synchronized void close() {
        isOpen = false;
    }

    public synchronized void open() {
        ++generation;
        isOpen = true;
        notifyAll();
    }

    // BLOCKS-UNTIL: opened-since(generation on entry)
    public synchronized void await() throws InterruptedException {
        int arrivalGeneration = generation;
        while (!isOpen && arrivalGeneration == generation)
            wait();
    }
}

子类的安全问题
要想支持子类化 那么在设计类时需要保证:如果在实施子类化时违背了条件通知或单次通知的某个需求 那么在子类中可以增加合适的通知机制来代表基类
对于状态依赖的类 要么将其等待和通知等协议完全向子类公开(并且写入正式文档) 要么完全阻止子类参与到等待和通知等过程中
另外一种选择就是完全禁止子类化 例如将类声明为final类型 或者将条件队列 锁和状态变量等隐藏起来 使子类看不见它们 否则 如果子类破坏了在基类中使用notify的方法 那么基类需要修复这种破坏

封装条件队列
通常 我们应该把条件队列封装起来 因而除了使用条件队列的类 就不能在其他地方访问它 否则 调用者会自以为理解了在等待和通知上使用的协议 并且采用一种违背设计的方式来使用条件队列 (除非条件队列对象对于你无法控制的代码来说是不可访问的 否则就不可能要求在单次通知中的所有等待线程都是同一类型的 如果外部代码错误地在条件队列上等待 那么可能通知协议 并导致一个 被劫持的 信号)

入口协议与出口协议
对于每个依赖状态的操作 以及每个修改其他操作依赖状态的操作 都应该定义一个入口协议和出口协议 入口协议就是该操作的条件谓词 出口协议则包括 检查被该操作修改的所有状态变量 并确认它们是否使某个其他的条件谓词变为真 如果是 则通知相关的条件队列

显式的Condition对象
在某些情况下 当内置锁过于灵活时 可以使用显式锁 正如Lock是一种广义的内置锁 Condition也是一种广义的内置条件队列

Condition接口

public interface Condition {
	void await() throws InterruptedException;
	boolean await(long time, TimeUnit unit)
              throws InterruptedException;
	long awaitNanos(long nanosTimeout) throws InterruptedException;
	void awaitUninterruptibly();
	boolean awaitUntil(Date deadline) throws InterruptedException;

	void signal();
	void signalAll();
}

内置条件队列存在一些缺陷 每个内置锁都只能有一个相关联的条件队列 因而在像BoundedBuffer这种类中 多个线程可能在同一个条件队列上等待不同的条件谓词 并且在最常见的加锁模式下公开条件队列对象 这些因素都使得无法满足在使用notifyAll时所有等待线程为同一类型的需求 如果想编写一个带有多个条件谓词的并发对象 或者想获得除了条件队列可见性之外的更多控制权 就可以使用显式的Lock和Condition而不是内置锁和条件队列 这是一种更灵活的选择
一个Condition和一个Lock关联在一起 就像一个条件队列和一个内置锁相关联一样 要创建一个Condition 可以在相关联的Lock上调用Lock.newCondition方法 正如Lock比内置加锁提供了更为丰富的功能 Condition同样比内置条件队列提供了更丰富的功能:在每个锁上可存在多个等待 条件等待可以是可中断的或不可中断的 基于时限的等待 以及公平的或非公平的队列操作
与内置条件队列不同的是 对于每个Lock 可以有任意数量的Condition对象 Condition对象继承了相关的Lock对象的公平性 对于公平的锁 线程会依照FIFO顺序从Condition.await中释放

特别注意:在Condition对象中 与wait notify和notifyAll方法对应的分别是await signal signalAll 但是 Condition对Objcet进行了扩展 因而它也包含wait和notify方法 一定要确保使用正确的版本——await和signal

使用显式条件变量的有界缓存

@ThreadSafe
public class ConditionBoundedBuffer <T> {
    protected final Lock lock = new ReentrantLock();
    // CONDITION PREDICATE: notFull (count < items.length)
    private final Condition notFull = lock.newCondition();
    // CONDITION PREDICATE: notEmpty (count > 0)
    private final Condition notEmpty = lock.newCondition();
    private static final int BUFFER_SIZE = 100;
    @GuardedBy("lock") private final T[] items = (T[]) new Object[BUFFER_SIZE];
    @GuardedBy("lock") private int tail, head, count;

    // BLOCKS-UNTIL: notFull
    public void put(T x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length)
                notFull.await();
            items[tail] = x;
            if (++tail == items.length)
                tail = 0;
            ++count;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    // BLOCKS-UNTIL: notEmpty
    public T take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0)
                notEmpty.await();
            T x = items[head];
            items[head] = null;
            if (++head == items.length)
                head = 0;
            --count;
            notFull.signal();
            return x;
        } finally {
            lock.unlock();
        }
    }
}

Synchronizer剖析
在ReentrantLock和Semaphore这两个接口之间存在许多共同点 这两个类都可以用做一个 阀门 即每次只允许一定数量的线程通过 并当线程到达阀门时 可以通过(在调用lock或acquire时成功返回) 也可以等待(在调用lock或acquire时阻塞) 还可以取消(在调用tryLock或tryAcquire时返回 假 表示在指定的时间内锁是不可用的或者无法获得许可) 而且 这两个接口都支持可中断的 不可中断的以及限时的获取操作 并且也都支持等待线程执行公平或非公平的队列操作

使用Lock来实现信号量

@ThreadSafe
public class SemaphoreOnLock {
    private final Lock lock = new ReentrantLock();
    // CONDITION PREDICATE: permitsAvailable (permits > 0)
    private final Condition permitsAvailable = lock.newCondition();
    @GuardedBy("lock") private int permits;

    SemaphoreOnLock(int initialPermits) {
        lock.lock();
        try {
            permits = initialPermits;
        } finally {
            lock.unlock();
        }
    }

    // BLOCKS-UNTIL: permitsAvailable
    public void acquire() throws InterruptedException {
        lock.lock();
        try {
            while (permits <= 0)
                permitsAvailable.await();
            --permits;
        } finally {
            lock.unlock();
        }
    }

    public void release() {
        lock.lock();
        try {
            ++permits;
            permitsAvailable.signal();
        } finally {
            lock.unlock();
        }
    }
}

AbstractQueuedSynchronizer
在基于AQS构建的同步器类中 最基本的操作包括各种形式的获取操作和释放操作 获取操作是一种依赖转态的操作 并且通常会阻塞 当使用锁或信号量时 获取 操作的含义就很直观 即获取的是锁或者许可 并且调用者可能会一直等待直到同步器类处于可被获取的状态 在使用CountDownLatch时 获取 操作意味着 等待并直到闭锁到达结束状态 而在使用FutureTask时 则意味着 等待并直到任务已经完成 释放 并不是一个可阻塞的操作 当执行 释放 操作时 所有在请求时被则塞的线程都会开始执行

AQS中获取操作和释放操作的标准形式

boolean acquire() throws InterruptedException {
	while(当前状态不允许获取操作) {
		if(需要阻塞获取请求) {
			如果当前线程不在队列中 则将其插入队列
			则塞当前线程
}
else
	返回失败
}
可能更新同步器的状态
如果线程位于队列中 则将其移出队列
返回成功
}
void release() {
更新同步器的状态
if(新的状态允许某个被则塞的线程获取成功)
	解除队列中一个或多个线程的阻塞状态
}

为了使支持条件队列的锁(例如ReentrantLock)实现起来更简单 AQS还提供了一些机制来构造与同步器相关联的条件变量

使用AbstractQueuedSynchronizer实现的二元闭锁

@ThreadSafe
public class OneShotLatch {
    private final Sync sync = new Sync();

    public void signal() {
        sync.releaseShared(0);
    }

    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(0);
    }

    private class Sync extends AbstractQueuedSynchronizer {
        protected int tryAcquireShared(int ignored) {
            // Succeed if latch is open (state == 1), else fail
            return (getState() == 1) ? 1 : -1;
        }

        protected boolean tryReleaseShared(int ignored) {
            setState(1); // Latch is now open
            return true; // Other threads may now be able to acquire

        }
    }
}

java.util.concurrent同步器类中的AQS
java.util.concurrent中的许多可阻塞类 例如ReentrantLock Semaphore ReentrantReadWriteLock CountDownLatch SynchronousQueue和FutureTask等 都是基于AQS构建的

ReentrantLock
ReentrantLock只支持独占方式的获取操作 因此它实现了tryAcquire tryRelease和isHeldExclusively

基于非公平的ReentrantLock实现tryAcquire

protected boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, 1)) {
            owner = current;
            return true;
        }
    }
    else if (current == owner) {
        setState(c+1);
        return true;
    }
    return false;
}

ReentrantLock还利用了AQS对多个条件变量和多个等待线程集的内置支持 Lock.newCondition将返回一个新的ConditionObject实例 这是AQS的一个内部类

Semaphore与CountDownLatch
Semaphore将AQS的同步状态用于保存当前可用许可的数量

Semaphore中的tryAcquireShared与tryReleaseShared

protected int tryAcquireShared(int acquires) {
            while (true) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }
 protected boolean tryReleaseShared(int releases) {
            while (true) {
                int p = getState();
                if (compareAndSetState(p, p + releases))
                    return true;
            }
        }

CountDownLatch使用AQS的方式与Semaphore很相似:在同步状态中保存的是当前的计数值 countDown方法调用release 从而导致计数值递减 并且当计数值为零时 解除所有等待线程的阻塞 await调用acquire 从而导致计数值递减 并且当计数值为零时 解除所有等待线程的则塞 await调用acquire 当计数器为零时 acquire将立即返回 否则将则塞

FutureTask
在FutureTask中 AQS同步状态被用来保存任务的状态 例如 正在运行 已完成或已取消 FutureTask还维护一些额外的状态变量 用来保存计算结果或者抛出的异常 此外 它还维护了一个引用 指向正在执行计算任务的线程(如果它当前处于运行状态) 因而如果任务取消 该线程就会中断

ReentrantReadWriteLock
ReadWriteLock接口表示存在两个锁:一个读取锁和一个写入锁 但在基于AQS实现的ReentrantReadWriteLock中 单个AQS子类将同时管理读取加锁和写入加锁 ReentrantReadWriteLock使用了一个16位的状态来表示写入锁的计数 并且使用了另一个16位的状态来表示读取锁的计数 在读取锁上的操作将使用共享的获取方法与释放方法 在写入锁上的操作将使用独占的获取方法与释放方法
AQS在内部维护一个等待线程队列 其中记录了某个线程请求的是独占访问还是共享访问 在ReentrantReadWriteLock中 当锁可用时 如果位于队列头部的线程执行写入操作 那么线程会得到这个锁 如果位于队列头部的线程执行读取访问 那么队列中在第一个写入线程之前的所有线程都将获得这个锁

小结
要实现一个依赖状态的类——如果没有满足依赖状态的前提条件 那么这个类的方法必须阻塞 那么最好的方式是基于现有的库类来构建 例如Semaphore.BlockingQueue或CountDownLatch 然而 有时候现有的库类不能提供足够的功能 在这种情况下 可以使用内置的条件队列 显式的Condition对象或者AbstractQueuedSynchronizer来构建自己的同步器 内置条件队列与内置锁是紧密绑定在一起的 这是因为管理状态依赖性的机制必须与确保状态一致性的机制关联起来 同样 显式的Condition与显式的Lock也是紧密地绑定到一起的 并且与内置条件队列相比 还提供了一个扩展的功能集 包括每个锁对应于多个等待线程集 可中断或不可中断的条件等待 公平或非公平的队列操作 以及基于时限的等待

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值