Java并发编程中状态依赖性的管理

  类库中包含了很多状态依赖性的类,例如FutureTask、Semaphore和BlockingQueue等。在这些类的一些操作中有着基于状态的前提条件,例如,不能从一个空的队列中删除元素,或者获取一个尚未结束的任务的计算结果,在这些操作可以执行之前,必须等到队列进入“非空”状态,或者任务进入“已完成”状态。

  在单线程程序中调用一个方法时,如果某个基于状态的前提条件未得到满足(例如“连接池必须非空”),那么这个条件将永远无法成真。因此,在编写顺序程序中的类时,要使得这些类在他们的前提条件未满足时就失败。但在并发程序中,基于状态的条件可能会由于其他线程的操作而改变:一个资源池可能在几条指令之前还是空的,但现在却变为非空的,因为另一个线程可能会返回一个元素到资源池。对于并发对象上依赖状态的方法,虽然有时候在前提条件不满足的情况下不会失败,但通常有一种更好的选择,即等待前提条件为真。

  依赖状态的操作可以一直阻塞直到可以继续执行,这比使他们先失败再实现起来要更为方便且不容易出错。内置的条件队列可以使线程一直阻塞,直到对象进入某个进程可以继续执行的状态,并且当被阻塞的线程可以执行时再唤醒它们。

  可阻塞的状态依赖操作的形式如下,这种加锁模式有些不太寻常,因为锁是在操作的执行过程中被释放与重新获取的。构成前提条件的状态变量必须由对象的锁来保护,从而使它们在测试前提条件的同时保持不变。如果前提条件尚未满足,就必须释放锁,以便其它线程可以修改对象的状态,否则,前提条件就永远无法变成真。在再次测试前提条件之前必须重新获得锁。

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

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这样的有界缓存。接下来介绍有界缓存的几种实现。 

2.有界缓存实现的基类

import org.apache.http.annotation.GuardedBy;
import org.apache.http.annotation.ThreadSafe;

/**
 * 有界缓存实现的基类
 */
@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;
	
	@SuppressWarnings("unchecked")
	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;
	}
}


3.将前提条件的失败传递给调用者

下面是一个简单的有界缓存实现,put和take方法都进行了同步以确保对缓存状态的独占访问,因为这两个方法在访问缓存时都采用“先检查,再运行”的逻辑策略。

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

简单的有界缓存实现

@ThreadSafe
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();
		}
		doPut(v);
	}
	
	public synchronized V take() throws BufferNullException{
		if(isEmpty()){
			throw new BufferNullException();
		}
		doTake();
	}
	
	

}


4.调用GrumpyBoundedBuffer的代码

while(true){
try{
V item = buffer.take();
//对item执行一些操作
break;
}catch(BufferEmptyException e){	Thread.sleep(SlEEP_GRANULARITY);
}}


5.通过轮询与休眠来实现简单的阻塞

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

@ThreadSafe
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(SLEEP_GRANULARITY);
		}
	}
	
	public V take() throws InterruptedException{
		while(true){
			synchronized(this){
				if(!isEmpty()){
					return doTake();
				}
			}
			Thread.sleep(SLEEP_GRANULARITY);
		}
	}
	

}


 

6.条件队列

  “条件队列”这个名字来源于:它使一组线程(称之为等待线程集合)能够通过某种方式来等待特定的条件变为真。传统队列的元素是一个个数据,而与之不同的是,条件队列中的元素是一个个正在等待相关条件的线程。

  正如每个Java对象都可以作为一个锁,每个对象同样可以作为一个条件队列,并且Object中的wait、notity和notifyAll方法就构成了内部条件队列的API。对象的内置锁与其内部条件队列是相互关联的,要调用对象X中条件队列的任何一个方法,必须持有对象X上的锁。这是因为“等待由状态构成的条件”与“维护状态一致性”这两种机制必须被紧密地绑定到一起:只有能对状态进行检查时,才能在某个条件上等待,并且只有能修改状态时,才能从条件等待中释放另一个线程。

  Object.wail会自动释放锁,并请求操作系统挂起当前线程,从而使其它线程能够获得这个锁并修改对象的状态。当被挂起的线程醒来时,它将会在返回之前重新获取锁。从直观上来理解,调用wait意味着“我要去休息了,但当发生特定的事情时唤醒我”,而调用通知方法就意味着“特定的事情发生了”。

  下类用了wait和notifyAll来实现一个有界缓存。这比使用“休眠”的有界缓存更简单,并且更高效(当缓存状态没有发生变化时,线程醒来的次数将更少),响应性也更高(当发生特定状态变化时将立即醒来)。这是一个较大的改进,但要注意:与使用“休眠”的有界缓存相比,条件队列并没有改变原来的语义。它只是在多个方面进行了优化:CPU效率、上下文切换开销和响应性等。如果某个功能无法通过“轮询和休眠”来实现,那么使用条件队列也无法实现,但条件队列使得在表达和管理状态依赖性时更加简单和高效。

import org.apache.http.annotation.ThreadSafe;

/**
 * 使用条件队列实现的有界循环
 * @Class_Name: BoundedBuffer
 */
@ThreadSafe
public class BoundedBuffer<V> extends BaseBoundedBuffer<V> {

	//条件谓词:not-full(!isFull())
	//条件谓词:not-empty(!isEmpty())
	protected BoundedBuffer(int capacity) {
		super(capacity);
	}
	
	//阻塞并直到:not-null
	public synchronized void put(V v) throws InterruptedException{
		while(isFull()){
			wait();
		}
		doPut(v);
		notifyAll();
	}
	
	//阻塞并直到:not-empty
	public synchronized V take() throws InterruptedException{
		while(isEmpty()){
			wait();
		}
		V v = doTake();
		notifyAll();
		return v;
	}

}








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值