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;}}
- Java并发编程中状态依赖性的管理
- Java并发框架——同步状态的管理
- Java并发编程(三)——线程的状态
- Java并发编程(一)线程的定义、状态、属性
- java并发编程的艺术(五)-----线程状态
- Java并发编程规则:有状态的线程安全对象在线程池中使用不一定就是线程安全的
- Java并发编程中Semaphore的用法
- Java并发编程中Exchanger的用法
- java中并发编程的一些总结
- JAVA并发编程5_线程状态
- [Java并发编程]-线程的六种状态及其状态转换
- [Java并发编程]-线程的六种状态及其状态转换
- java并发编程之线程管理
- Java 并发编程(三)线程管理
- java并发编程中CountDownLatch和CyclicBarrier的使用 - [java]
- Java并发编程的艺术(四)——线程的状态
- Java并发编程的艺术(四)——线程的状态
- JavaEE中的依赖性管理
- eclipse配置Tomcat服务器server locations的方法
- iOS APP 支持IPv6-only的注意事项及兼容性考虑
- 学习java基础关键字之synchronized和volatile
- 第十一届湖南省大学生程度设计竞赛部分题解
- 火星坐标转百度坐标
- Java并发编程中状态依赖性的管理
- linux系统编程之线程(二)
- 穷人做事、富人做市、商人做势[趋势]
- apple IOS的base64编解码
- 【幻化万千戏红尘】qianfengDay22-java基础学习:线程安全、synchronized、死锁
- nand flash_笔记
- 100V 降压恒流led驱动 FP7175
- light oj 1369 - Answering Queries
- HTML5 Web存储 sessionStorage localStorage