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

来源:互联网 发布:google chrome mac 编辑:程序博客网 时间:2024/05/22 06:49

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

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

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

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

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

acquire lock on object statewhile(precondition does not hold){release lockwait until precondition might holdoptionally fail if interrupted or timeout expiresreacquire lock}perform actionrelease lock


在生产者-消费者的设计中经常会使用像ArrayBlockingQueue这样的有界缓存。接下来介绍有界缓存的几种实现。

2.有界缓存实现的基类

import org.apache.http.annotation.GuardedBy;import org.apache.http.annotation.ThreadSafe;/** * 有界缓存实现的基类 */@ThreadSafepublic 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方法都进行了同步以确保对缓存状态的独占访问,因为这两个方法在访问缓存时都采用“先检查,再运行”的逻辑策略。

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

简单的有界缓存实现

@ThreadSafepublic 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.通过轮询与休眠来实现简单的阻塞

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

@ThreadSafepublic 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 */@ThreadSafepublic class BoundedBuffer<V> extends BaseBoundedBuffer<V> {//条件谓词:not-full(!isFull())//条件谓词:not-empty(!isEmpty())protected BoundedBuffer(int capacity) {super(capacity);}//阻塞并直到:not-nullpublic synchronized void put(V v) throws InterruptedException{while(isFull()){wait();}doPut(v);notifyAll();}//阻塞并直到:not-emptypublic synchronized V take() throws InterruptedException{while(isEmpty()){wait();}V v = doTake();notifyAll();return v;}}








0 0