java并发-使用内置条件队列实现简单的有界缓存

内置锁和内置条件队列一起,一个简单的应用是创建可阻塞的有界缓存区,java并发包的BlockingQueue就是一个利用Lock和显式条件队列实现的可阻塞的有界队列。总结内置锁和内置条件的原理,这里我们用另一种方式实现简单的可阻塞缓存。源码如下:

首先,创建一抽象有界缓存类ABoundedBuffer,提供插入和删除的基本实现。

/**
 * @title       :ABoundedBuffer
 * @description :有界缓存抽象类
 * @update      :2014-12-30 上午9:29:33
 * @author      :172.17.5.73
 * @version     :1.0.0
 * @since       :2014-12-30
 */
public abstract class ABoundedBuffer<V> {
	private final V[] buf;
	private int tail;
	private int head;
	private int count;

	protected ABoundedBuffer(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;
	}
}

其次,利用内置条件队列,编写子类实现可阻塞的插入和删除操作。插入操作,依赖的条件是缓存非满,当条件不满足时,调用wait方法挂起线程,一旦插入成功,说明缓存非空,则调用notifyAll方法唤醒等待非空的线程。删除操作,依赖的条件是非空,当条件不满足时,同样挂起等待,一旦删除成功,说明缓存非满,唤起等待该条件的线程。简单的源码如下:

import java.util.Date;

/**
 *
 * @title       :InnerConditionQueue
 * @description :使用内置条件队列,实现简单的有界缓存
 *               通过对象的wait和notify来实现挂起
 *               锁对象是this,调用wait/notify的对象是同一个对象。
 *               三元关系(锁、wait/notify、条件谓词)
 *               缺陷:
 *               线程从wait中被唤醒时,并不代码条件谓词为真,此时还是需要再判断条件。所以必须在循环中调用wait
 *               每次醒来时都判断谓词的真假。
 *               谓词:对客体的描述或说明(是什么、怎么样、做什么),描述客体的本质、关系、特性等的词项。
 * @update      :2014-12-18 下午4:18:06
 * @author      :172.17.5.73
 * @version     :1.0.0
 * @since       :2014-12-18
 */
public class InnerConditionQueue<V> extends ABoundedBuffer<V> {

	protected InnerConditionQueue(int capacity) {
		super(capacity);
	}

	public synchronized void put(V v) throws InterruptedException{
		while(isFull()){
			System.out.println(new Date()+" buffer is Full thread wait:"+Thread.currentThread().getName());
			wait();
		}

		doPut(v);
		notifyAll();
	}

	public synchronized V take() throws InterruptedException{
		while(isEmpty()){
			System.out.println(new Date()+" buffer is empty thread wait:"+Thread.currentThread().getName());
			wait();
		}

		V v = doTake();
		//每当在等待一个条件时,一定要确保在条件谓词变为真时,通过某种方式发出通知
		notifyAll();
		System.out.println(new Date()+" "+Thread.currentThread().getName()+" take:"+v);
		return v;
	}
}

最后,编写测试代码,创建一个大小为2的缓冲区,启动三个线程执行插入操作,当第三个线程执行时会因为缓存已满而挂起,主线程删除两个记录后,等待线程被唤醒成功插入。当缓存空的时候,之后的删除操作将被阻塞直到有新的记录插入为止。测试代码如下:

import java.util.Date;

public class Main {
	public static void main(String[] args) {
		final InnerConditionQueue<String> bu = new InnerConditionQueue<String>(2);

		Thread t1 = new Thread(new Runnable(){
			@Override
			public void run() {
				try {
					bu.put("hello1");
				} catch (InterruptedException execption) {
					System.out.println("intercetp1:"+Thread.currentThread().getName());
				}
			}
		});
		Thread t2 = new Thread(new Runnable(){

			@Override
			public void run() {
				try {
					bu.put("hello2");
				} catch (InterruptedException execption) {
					System.out.println("intercetp2:"+Thread.currentThread().getName());
				}
			}
		});
		Thread t3 =  new Thread(new Runnable(){
			@Override
			public void run() {
				try {
					bu.put("hello3");
					Thread.sleep(50000);
					bu.put("last one...");
				} catch (InterruptedException execption) {
					System.out.println("intercetp3:"+Thread.currentThread().getName());
				}
			}
		});

		t1.start();
		t2.start();
		t3.start();

		try {
			Thread.sleep(5000);
			bu.take();
			bu.take();
			bu.take();
			bu.take();
		} catch (InterruptedException execption) {
			execption.printStackTrace();
		}

		System.out.println(new Date()+" main over...");
	}
}

执行结果:t3的第一个put操作会因为缓存已满而阻塞,5秒后主线程删除两个操作后,重新被唤醒。主线程的第四个bu.take()操作会因为缓存为空而阻塞,直到t3在50秒后重新插入"last one"后被唤醒,操作结束。

Tue Dec 30 10:23:53 CST 2014 buffer is Full thread wait:Thread-2
Tue Dec 30 10:23:58 CST 2014 main take:hello1
Tue Dec 30 10:23:58 CST 2014 main take:hello2
Tue Dec 30 10:23:58 CST 2014 buffer is empty thread wait:main
Tue Dec 30 10:23:58 CST 2014 main take:hello3
Tue Dec 30 10:23:58 CST 2014 buffer is empty thread wait:main
Tue Dec 30 10:24:48 CST 2014 main take:last one...
Tue Dec 30 10:24:48 CST 2014 main over...

结论:BlockingQueue的子类ArrayBlockingQueue是使用ReentrantLock和ObjectCondition实现的可阻塞队列,该实现选取显式锁和显式条件队列的原因是很显然,这个队列的入队和出队操作依赖两个条件,而ReentrantLock可以关联多个条件队列。它与我们用Object的内置队列相比,巧妙之处在于:线程会在其等待的条件队列中等待。我们的例子中非空和非满这两种条件都关联着同一个条件队列,当一个线程由于其他线程调用了notifyAll而被唤醒时,并不意味着它等待的条件已经为真了,这也是内置条件队列的局限所在。

时间: 2024-11-10 08:18:13

java并发-使用内置条件队列实现简单的有界缓存的相关文章

并发编程 17—— 使用内置条件队列实现简单的有界缓存

并发编程 01—— ConcurrentHashMap 并发编程 02—— 阻塞队列和生产者-消费者模式 并发编程 03—— 闭锁CountDownLatch 与 栅栏CyclicBarrier 并发编程 04—— Callable和Future 并发编程 05—— CompletionService : Executor 和 BlockingQueue 并发编程 06—— 任务取消 并发编程 07—— 任务取消 之 中断 并发编程 08—— 任务取消 之 停止基于线程的服务 并发编程 09——

Java 并发:内置锁 Synchronized

摘要: 在多线程编程中,线程安全问题是一个最为关键的问题,其核心概念就在于正确性,即当多个线程访问某一共享.可变数据时,始终都不会导致数据破坏以及其他不该出现的结果.而所有的并发模式在解决这个问题时,采用的方案都是序列化访问临界资源 .在 Java 中,提供了两种方式来实现同步互斥访问:synchronized 和 Lock.本文针对 synchronized 内置锁 详细讨论了其在 Java 并发 中的应用,包括它的具体使用场景(同步方法.同步代码块.实例对象锁 和 Class 对象锁).可重

java web 程序---内置对象application的log方法的使用

application的主要方法里,有log方法,是日志文件里可以查看到信息的. 当老师写好代码后,他发现在tomact里的log目录下找不到信息,原因是:我们用myeclipse这个客户端软件,应该把服务器关闭,而是去bin目录下,手动开启服务器 即startup.bat.这个文件,然后在log目录下查到了信息: 这里有截图,我输入的用户名,这里当程序运行后是个空白页面,只有到log目录下才可以查看到信息. 代码: login.jsp ? 1 2 3 4 5 6 7 <body>     &

Java Server Pages 内置对象

JSP九大内置对象: a.JSP内置对象是Web容器创建的一组对象,[不使用new关键字]就可以使用的内置对象.例如:out b.JSP九大内置对象: 五大常用对象:   out,request,response,session,application 其余四大对象:   Page,pageContext,exception,config 1.out内置对象: 2.request内置对象,常用方法如下: request 对象是 javax.servlet.httpServletRequest类型

Java EE JSP内置对象及表达式语言

一.JSP内置对象 JSP根据Servlet API规范提供了一些内置对象,开发者不用事先声明就可使用标准变量来访问这些对象. JSP提供了9种内置对象: (一).request 简述: JSP编程中最常用的对象,代表来自客户端的请求,调用request对象相应的方法可以获取关于客户请求的信息. 常见方法的用法示例: 1 <font size=5> 2 <br>客户端使用的协议是: 3 <% 4 String protocol = request.getProtocol();

9.Java web&mdash;JSP内置对象

容器内置了9大对象,这些对象在jsp页无需实例化,可以直接使用. 分别为request. response .session. application .out. pageContext .config .page. exception 1)request对象 request封装了由客户端请求的http所有细节,包括HTTP头信息,系统信息,请求方式,请求参数 request.getParameter("参数name");  //获取请求的url参数.不存在此参数返回null,存在参数

JS中的内置对象简介与简单的属性方法

JS中的数组: 1.数组的概念: 数组是在内存中连续存储的多个有序元素的结构,元素的顺序称为下标,通过下标查找对应元素 2.数组的声明: ①通过字面量声明var arr1 = [,,,,] JS中同一数组可以储存多种不同的数据类型(但,同一数组一般只用于存放同种数据类型) 例如var arr1 = [1,"2",true,{"name":"啦啦啦"},[1,2]]; ②通过new关键字声明:var arr2 = new Array(参数); &g

利用PHP内置函数制作一个简单的验证码

因为这两天学习了一些PHP的内置函数,所以今天就用一些内置函数配合数组来简单的制作一个随机验证码的效果. 例如:2dT5     T22c.... 分析:首先分析验证码的组成: 1.验证码是由数字1-9,大写字母A-Z,小写字母a-z 中随机生成的. 2.我先创建一个包含指定范围单元的数组.(这里应该是三个:数字,大写字母,小写字母). 3.我可以将这些数组合并成一个大的数组 4.随机打乱该函数.ps:其实觉得在这里再做一步将数组随机打乱,感觉也没有什么必要啊!因为后面我们做的不也是随机抽取吗?

Django 查询集的过滤内置条件

条件选取querySet的时候,filter表示=,exclude表示!=.querySet.distinct() 去重复__exact 精确等于 like 'aaa' __iexact 精确等于 忽略大小写 ilike 'aaa' __contains 包含 like '%aaa%' __icontains 包含 忽略大小写 ilike '%aaa%',但是对于sqlite来说,contains的作用效果等同于icontains.__gt 大于__gte 大于等于__lt 小于__lte 小于