作者:麦芽糖的-寂寞 | 来源:互联网 | 2023-08-15 11:50
一、通过轮询与休眠的方式实现简单的有界缓存publicvoidput(Vv)throwsInterruptedException{while(true){轮询synchronize
一、通过轮询与休眠的方式实现简单的有界缓存
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 {
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关联