java并发编程(7)构建自定义同步工具及条件队列
构建自定义同步工具
一、通过轮询与休眠的方式实现简单的有界缓存
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); } }
二、通过条件队列:wait notify方法
注意:实际逻辑与上面没区别;且无法通过轮询和休眠方式实现的,也无法通过条件队列实现
// BLOCKS-UNTIL: not-full public synchronized void put(V v) throws InterruptedException { while (isFull()) { wait(); //等待:释放锁且等待;等待通知后获取锁 并继续判断条件谓词 } doPut(v); notifyAll(); //通知:通知释放所有等待 } // 问题,不好分析,在put 和 take操作后,通知了所有等待条件队列 // 而且让所有 等待都要去判断 条件谓词; // 如我put完成,只需要通知take正在等待的,而无需通知put正在等待的(反之亦然),因为只有take后,缓存才有可能不是满的,才要去判断条件谓词 public synchronized V take() throws InterruptedException { while (isEmpty()) { wait(); } V v = doTake(); notifyAll(); return v; }
三、内置条件队列的使用
条件谓词:对象在哪个条件下等待
条件队列:每次唤醒时,必须重新检查条件谓词
四、显式锁Condition:自定义条件队列
内置条件队列:每个内置锁只能有一个相关联的条件队列
所以:多个线程在一个条件队列上等待不同的条件谓词不好解决
方法:wait、notify、notifyAll
而Condition:每个Lock,可以有任意的Condition
方法:await、signal、signalAll
优化内置的条件队列:
public class ConditionBoundedBuffer <T> { protected final Lock lock = new ReentrantLock(); // 条件谓词 未满 private final Condition notFull = lock.newCondition(); // 条件谓词 非空 private final Condition notEmpty = lock.newCondition(); private static final int BUFFER_SIZE = 100; private final T[] items = (T[]) new Object[BUFFER_SIZE]; private int tail, head, count; // BLOCKS-UNTIL: notFull public void put(T x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); //当缓存满了,等待有线程取出缓存;等待未满谓词通知(只有take才有可能让缓存不满) 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(); } } }
五、AQS:AbstractQueuedSynchronizer
先来看一个例子:
//通过 lock+条件队列实现信号量Semaphore 许可 public class SemaphoreOnLock { private final Lock lock = new ReentrantLock(); // CONDITION PREDICATE: permitsAvailable (permits > 0) private final Condition permitsAvailable = lock.newCondition(); 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) //当许可<0时 等待 许可释放 permitsAvailable.await(); --permits; } finally { lock.unlock(); } } public void release() { lock.lock(); try { ++permits; //释放许可 permitsAvailable.signal(); } finally { lock.unlock(); } } }
AQS负责管理同步器类中的状态,它管理了一个整数状态信息;
如:通过AQS实现简单的闭锁
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 { //继承AQS protected int tryAcquireShared(int ignored) { // 判断状态 return (getState() == 1) ? 1 : -1; } protected boolean tryReleaseShared(int ignored) { //释放状态 setState(1); // 打开闭锁 return true; // 其他线程可以获取该闭锁 } } }
小结
1.最好使用现有的类库来构建状态类:如缓存
2.如果现有类不能满足功能:如从一个空队列中删除或获取元素会抛出异常,当然也有阻塞等待
那么使用Lock、内置的条件队列、Condition显式的条件队列、AQS等来构建自己的同步器也是可行的
内置条件队列与内置锁关联、显式的条件队列与Lock关联