本文共 5015 字,大约阅读时间需要 16 分钟。
构成前提条件的状态变量必须由对象的锁来保护,从而使他们在测试前提条件的同时保持不变;如果前提条件尚未满足,就必须释放锁,以便其它线程可以修改对象的状态,否则,前提条件就永远无法变成真。在再次测试前提条件之前,必须重新获得锁
将前提条件的失败传递给调用者,调用者可以选择休眠等待、自旋等待或者调用Thread.yield
可以通过简单的“轮询与休眠”重试机制实现阻塞,同时将前提条件的管理操作封装起来
使得一组线程(等待线程集合)能够通过某种方式来等待特定的条件变成真;条件队列中的元素是一个个正在等待相关条件的线程
每个Java对象可以作为一个锁,也可以作为一个条件队列,Object的wait/notify/notifyAll方法构成了内部条件队列的API。对象的内置锁与其内部条件队列是相互关联的,要调用对象X中条件队列的任何一个方法,必须持有对象X上的锁
Object.wait会自动释放锁,并请求操作系统挂起当前线程,从而使其他线程能够获得这个锁并修改对象的状态。当被挂起的线程醒来时,他将在返回之前重新获得锁
public synchronized void put(V v) throws InterruptedException{ while(isFull()){ System.out.println(new Date()+" buffer 满了, thread wait:"+Thread.currentThread().getName()); wait(); } doPut(v); System.out.println(new Date()+" "+Thread.currentThread().getName()+" 放入 :"+v+" "); notifyAll(); } public synchronized V take() throws InterruptedException { while(isEmpty()){ System.out.println(new Date()+" buffer 为空, thread wait:"+Thread.currentThread().getName()); wait(); } notifyAll(); //每当在等待一个条件时,一定要确保在条件谓词变为真时,通过某种方式发出通知 V v = doTake(); System.out.println(new Date()+" "+Thread.currentThread().getName()+" 取出 :"+v); return v; }
void stateDependentMethod() throws InterruptedException{ synchronized(lock){ while(!conditionPredicate()) lock.wait(); //现在对象处于合适的状态 }}
当使用条件等待时(Object.wait/Condition.await)
调用notify时,JVM会从这个条件队列上等待的多个线程中选择一个来唤醒(单一的通知很容易导致信号丢失),而调用notifyAll则会唤醒所有在这个条件队列上等待的线程
只有同时满足以下两个条件时,才能用单一的notify:
所有等待线程的类型都相同:只有一个条件谓词与条件队列相关,并且每个线程在从wait返回后将执行相同的操作
单进单出:在条件变量上的每次通知,最多只能唤醒一个线程来执行
用“入口协议”和“出口协议”描述wait和notify方法的正确使用:
Lock是一种广义的内置锁,Condition也是一种广义的内置条件队列
与内置条件队列不同的是,对于每个Lock,可以有任意数量的Condition对象;Condition比内置条件队列提供了更丰富的功能:在每个锁上可存在多个等待、条件等待可以是可中断的或不可中断的、基于时限的等待,以及公平的或非公平的队列操作
当使用显式的Lock和Condition时,必须满足锁、条件谓词、条件变量之间的三元关系:条件谓词中包含的变量必须由Lock保护,并且在检查条件谓词以及调用await和signal时,必须持有Lock对象
/** * 使用显式条件变量的有界缓存 */public class ConditionBoundedBuffer{ protected final Lock lock = new ReentrantLock(); //条件谓词:notFull (count < items.length) private final Condition notFull = lock.newCondition(); //条件谓词: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; //阻塞并直到: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(); } } //阻塞并直到: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; notEmpty.signal(); return x; }finally{ lock.unlock(); } }}
LOCK的实现类其实都是构建在AbstractQueuedSynchronizer上,每个Lock实现类都持有自己内部类Sync的实例,而这个Sync就是继承AbstractQueuedSynchronizer(AQS)。
提供 volatile 变量 state;用于同步线程之间的共享状态。通过 CAS 和 volatile 保证其原子性和可见性。
/** * 同步状态 */ private volatile int state; /** *cas */ protected final boolean compareAndSetState(int expect, int update) { // See below for intrinsics setup to support this return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }
同步器是实现锁的关键,利用同步器将锁的语义实现,然后在锁的实现中聚合同步器。可以这样理解:锁的API是面向使用者的,它定义了与锁交互的公共行为,而每个锁需要完成特定的操作也是透过这些行为来完成的(比如:可以允许两个线程进行加锁,排除两个以上的线程),但是实现是依托给同步器来完成;同步器面向的是线程访问和资源控制,它定义了线程对资源是否能够获取以及线程的排队等操作。锁和同步器很好的隔离了二者所需要关注的领域,严格意义上讲,同步器可以适用于除了锁以外的其他同步设施上(包括锁)。
java.util.concurrent中的很多可阻塞类都是基于AQS构建的
ReentrantLock:
Semaphore:
CountDownLatch:
FutureTask:
ReentrantReadWriteLock:
转载地址:http://bmkvb.baihongyu.com/