并发中对状态依赖性的管理_轮询与休眠
在单线程程序中调用一个方法时,如果基于某个状态的前提条件未得到满足(例如“连接池必须非空”),那么这个条件永远无法成真。所以这些方法调用在前提条件未满足时就失败。
但在并发程序中,基于状态的条件可能由于其他线程的操作而改变:一个资源池可能在几条指令前是空的,但现在却变为非空的,因为另一个线程可能会返回一个元素到资源池。
对于并发对象上的依赖状态的方法,虽然有时候在前提条件不满足的情况下不会失败,但通常有一种更好的选择,即等待前提条件为真。
示例:将前提条件的失败传递给调用者
首先将定义两个异常,一个异常表示缓存已满,一个异常表示缓存为空。
BufferEmptyException.java
package sync;
public class BufferEmptyException extends Exception {
public BufferEmptyException() {
super(); //To change body of overridden methods use File | Settings | File Templates.
}
public BufferEmptyException(String message) {
super(message); //To change body of overridden methods use File | Settings | File Templates.
}
public BufferEmptyException(String message, Throwable cause) {
super(message, cause); //To change body of overridden methods use File | Settings | File Templates.
}
}
BufferFullException.java
package sync;
public class BufferFullException extends Exception {
public BufferFullException() {
super(); //To change body of overridden methods use File | Settings | File Templates.
}
public BufferFullException(String message) {
super(message); //To change body of overridden methods use File | Settings | File Templates.
}
public BufferFullException(String message, Throwable cause) {
super(message, cause); //To change body of overridden methods use File | Settings | File Templates.
}
}
有界缓存实现的基类,
package sync;
public abstract class BaseBoundedBuffer<V> {
private final V[] buf;
private int tail;
private int head;
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;
}
}
当不满足前提条件时,有界缓存不会执行相应的操作。
package sync;
public class GrumpyBoundedBuffer<V> extends BaseBoundedBuffer<V> {
protected GrumpyBoundedBuffer(int capacity) {
super(capacity);
}
public synchronized void put(V v) throws BufferFullException {
if (isFull()) {
throw new BufferFullException("buffer is full");
}
doPut(v);
}
public synchronized V take() throws BufferEmptyException {
if (isEmpty()) {
throw new BufferEmptyException("buffer is empty");
}
V v = doTake();
return v;
}
}
测试方法:(下面代码是单线程环境)
public static void main(String args[]) {
GrumpyBoundedBuffer<String> buffer = new GrumpyBoundedBuffer(10);
while (true) {
try {
buffer.put(new String("sdsdsd"));
} catch (BufferFullException e) {
e.printStackTrace();
break;
}
}
while (true) {
try {
System.out.println(buffer.take());
} catch (BufferEmptyException e) {
e.printStackTrace();
break;
}
}
}
这样简单实现了一个有界缓存。这样的坏处就是将前提条件的是失败传递给了调用者,调用者必须做好捕获异常的准备,并且每次缓存操作时都需要重试(上面代码没有进行重试的逻辑)。
在这里当发生异常(前提条件的失败),程序break,退出,没有进入休眠或忙等待。
示例:通过轮询与休眠实现简单的阻塞
下面这段代码尝试通过put和take方法来实现一种简单的“轮询与休眠”重试机制,从而使调用者无须在每次调用时都实现重试逻辑。如果缓存为空,那么take将休眠并直到另一个线程在缓存中放入一些数据;如果缓存是满的,那么put将休眠并直到另一个线程从缓存中移除一些数据,以便有空间容纳新的数据。
package sync;
public class SleepyBoundedBuffer<V> extends BaseBoundedBuffer<V> {
protected SleepyBoundedBuffer(int capacity) {
super(capacity);
}
public void put(V v) throws InterruptedException {
while (true) {
synchronized (this) {
if (!isFull()) {
doPut(v);
return;
}
}
//当有界缓存满,进入休眠,等待下次重试
Thread.sleep(1000);
System.out.println("put retry");
}
}
public V take() throws InterruptedException {
while (true) {
synchronized (this) {
if (!isEmpty()) {
return doTake();
}
}
//当有界缓存为空,进入睡眠,等待下次获取
Thread.sleep(1000);
System.out.println("take retry");
}
}
}
测试代码:
public static void main(String args[]) {
final SleepyBoundedBuffer buffer = new SleepyBoundedBuffer(10);
//线程t2打印缓存中的消息
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
System.out.println(buffer.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
//线程t1放入缓存消息
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
buffer.put(new String("sadsasd"));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t2.start();
t1.start();
}
运行这段代码你会最终看到一直在take retry。休眠的时间你可以根据具体情况选择一个合理的休眠时间,以提高程序的响应时间。
在上面的代码中,没有处理InterruptedException。当一个方法由于等待某个条件变成真而阻塞时,需要提供一种取消机制(取消阻塞)。与大多数具备良好行为的阻塞库方法一样,SleepyBoundedBuffer通过中断来支持取消,如果该方法被中断,那么提前返回并抛出InterruptedException。
关于中断:请看http://my.oschina.net/xinxingegeya/blog/300017
后话:这种通过轮询与休眠来实现阻塞操作的过程需要付出大量的努力。
==========END==========