JAVA开发实战状态依赖性的管理之阻塞队列的实现
类库本身包含了许多存在状态依赖性的类。如FutureTask,BlockingQueue等。这些类中的一些操作,会基于状态的前提条件。比如,不能从一个空的队列删除元素或获取一个尚未结束的任务的计算结果。这两个操作执行之前,必须等到队列进入非空状态或者任务进入已完成状态。我们创建状态依赖类最简单的方法是在类库的基础上进行构造。但是如果类库没有你想要的功能,那么还可以利用Java语言和类库提供的底层机制来构造自己的同步机制。
所以,本篇要介绍如何去构造一个自己的状态依赖类。从最简单的构造一步一步介绍到复杂的规范的构造,从而了解这个过程,知道是如何得到最后的结果。
状态依赖性的管理
可阻塞的状态依赖操作如下伪代码所示:
acquire lock on object state //首先获取锁 while (precondition does not hold) { //前提条件是否满足,不满足则一直循环重试 release lock //释放锁 wait until precondition might hold //等待知道满足前提条件 optionally fail if interrupted or timeout expire //中断或者超时,各种异常 reacquire lock //重新获取锁 } perform action //执行任务 release lock //释放锁
获取锁,检查条件是否满足,如果不满足,则释放锁进入阻塞状态,直到条件满足或者中断、超时等,重新获取锁。执行任务,释放锁。
现在看这个伪代码可能还不能够直观的理解,没事,往下看,看完这篇文章就知道他的意思了,每个操作都是这个伪代码架构构造的。
ArrayBlockingQueue是一个有界缓存,提供的两个操作,put 和 take。 它们都包含一个前提条件:不能将元素放入到已满的缓存中,不能从空缓存中获取元素。恩,我们的目标就是构造这样一个ArrayBlockingQueue。
接下来,介绍2种有界缓存的实现,它们采用不同的方法来处理前提条件不满足的情况。
首先,来看下面一个基类 BaseBoundeBuffer, 后面的实现都扩展这个基类。它是一个基于数组的循环缓存,包含的变量 buf、head、tail、count都由缓存的内置锁保护。它还提供了同步的 doPut 和 doTake 方法,并在子类中,通过这些方法来实现 put 和 take 操作,底层的状态将对子类隐藏。
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]; count = 0; } 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; } }
第一种有界缓存的实现,对 put 和 take 方法都进行同步,先检查后执行,失败则抛出异常。
public class GrumpyBoundedBuffer<V> extends BaseBoundedBuffer{ 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 BufferFullException { if(isEmpty()) throw new BufferFullException(); return (V) doTake(); } }
如上所示,对于前提条件不满足的情况,都直接抛出异常,这里所谓的异常,是指缓存满或空。实际上来讲,这异常不代表着程序出错,打个比方,看到红灯并不意味着信号灯出现了异常,而是等待直到绿灯在过马路。所以,这里的意思是要求在调用方捕获异常,并每次缓存操作时都需要重试。
我们直接来看下面的客户端调用代码:
private static GrumpyBoundedBuffer gbb = new GrumpyBoundedBuffer(5); ...while(true) { try { V item = gbb.take(); break; } catch(BufferEmptyException e) { Thread.sleep(500); } }
说白了就是在不满足前提条件的情况下,再试一次,直到条件满足,让看起来能够达到阻塞的效果。但是这种情况,调用者必须自行处理前提条件是失败的情况,并且一直占用CPU。这里的问题是调用者使用这个队列会很麻烦!
第二种方法,SleepyBoundedBuffer 通过轮询和休眠来实现简单的阻塞的重试机制,从而使得调用者剥离了重试机制,简化了对缓存的使用。请看下面的代码清单:
public class SleepyBoundedBuffer<V> extends BaseBoundedBuffer{ protected SleepyBoundedBuffer(int capacity) { super(capacity); // TODO Auto-generated constructor stub } public void put(V v) throws InterruptedException { while(true) { synchronized(this) { if(!isFull()) { doPut(v); return; } } Thread.sleep(200); } } public V take() throws InterruptedException{ while(true) { synchronized(this) { if(!isEmpty()) { return (V) doTake(); } } Thread.sleep(200); } } }
从调用者的角度看,这种方法可以很好的运行。假如某个操作满足前提条件,则立即执行,否则就阻塞。调用者无需处理失败和重试,但是调用者仍然需要处理InterruptedException。与大多数具备良好行为的阻塞库方法一样,SleepyBoundedBuffer 通过中断来支持取消。
SleepyBoundedBuffer的问题在于,睡眠时间设置多长才是合理的?如何才能达到性能的最优?如下图所示,B线程设置条件为真的,但此时A仍然在睡眠,这个睡眠就是性能的瓶颈所在了。
恩,有没有某种方法可以达到,当条件为真时,线程立即醒过来执行呢?
卖个关子,下一篇为你讲解!
以上是JAVA开发实战状态依赖性的管理之阻塞队列的实现的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

公司安全软件导致部分应用无法正常运行的排查与解决方法许多公司为了保障内部网络安全,会部署安全软件。...

将姓名转换为数字以实现排序的解决方案在许多应用场景中,用户可能需要在群组中进行排序,尤其是在一个用...

系统对接中的字段映射处理在进行系统对接时,常常会遇到一个棘手的问题:如何将A系统的接口字段有效地映�...

在使用IntelliJIDEAUltimate版本启动Spring...

Java对象与数组的转换:深入探讨强制类型转换的风险与正确方法很多Java初学者会遇到将一个对象转换成数组的�...

在使用MyBatis-Plus或其他ORM框架进行数据库操作时,经常需要根据实体类的属性名构造查询条件。如果每次都手动...

电商平台SKU和SPU表设计详解本文将探讨电商平台中SKU和SPU的数据库设计问题,特别是如何处理用户自定义销售属...

Redis缓存方案如何实现产品排行榜列表的需求?在开发过程中,我们常常需要处理排行榜的需求,例如展示一个�...
