动手写一个阻塞队列

之前看队列,都是停留在看和使用的阶段。再次看队列的时候,忽然发现并没有深入到底层。比如:阻塞队列时如何阻塞的呢?是监听,还是等待呢?然后看着看着就看到了Lock和ReentrantLock,为什么不使用synchronized呢?为什么使用Condition,Condition是什么呢?wait,notify,notifyAll和await,signal,signalAll有什么区别呢?

能不能自己写一个阻塞队列呢?

有时候是需要动手写写的,写着写着发现已经理解了,所以这里就写一下吧。

1.第一步,了解队列容器
并发容器中有两个比较常用的队列容器,分别是ArrayBlockingQueue和LinkedBlockingQueue,
ArrayBlockingQueue内部是使用数组实现了,LinkedBlockingQueue内部是使用链表实现的。
数组和链表熟不熟悉呢?如果不熟悉,可不可以直接使用已经有的集合类实现呢?
所以本次尝试着使用ArrayList作为队列容器。

2.第二步,了解队列提供的方法

一个队列,起码具有两个方法:存和取。
所以这里就定义两个方法,add()往队列中存数据,take()从队列中取数据。

3.第三步,阻塞功能

take()方法应该具有阻塞功能,在没有数据的时候处于等待状态,在有数据的时候立刻可以从队列中取出数据。

4.第四步,如何阻塞

可不可以设计成wait()和notify()呢?在take()方法如果取不出数据的时候调用wait()进行阻塞,在add()方法存数据的时候,调用notify()方法,唤醒wait()。嘿,貌似可以哈。

5.第五步,synchronized和ReentrantLock

使用wait()和notify()方法貌似有些问题呢?
wait()和notify()需要和synchronized配合使用,wait()阻塞会释放锁,但是notify()却不会释放锁。如果有一个生产者一直占用着存方法,即使唤醒了wait()线程,那wait()线程也取不出数据,只有等到存方法执行完成,释放了锁,wait()才可以取数据。这个是有问题的,所以选择得ReentrantLock。

6.第六步,ReentrantLock的Condition

wait()和notify()都是数据Object对象的方法,如果不能用,那该怎么监听呢?这时候需要用到Condition对象,可以通过Condition唤醒指定的某个线程。

7.第七步,开始写自己的阻塞队列

/**
 * 仿照阻塞队列,实现自己的阻塞队列。
 * 中间会使用到Lock接口,ReentrantLock,Condition
 */
public class SimpleBlockingQueue {

	//队列容器
	private List<Integer> container = new ArrayList<Integer>();
	//用于记录容器中元素的个数
	private int count;
	//申明锁
	private Lock lock = new ReentrantLock();
	//标识,可以表示具体的线程
	private final Condition conditionNull = lock.newCondition();
	private final Condition conditionFull = lock.newCondition();

	/**
	 * 将数据加入到队列中
	 * @param item
	 * @throws InterruptedException
	 */
	public void add(Integer item) throws InterruptedException {
		if(item == null){
			throw new NullPointerException();
		}
		//申明可中断锁,简单起见也可以直接使用lock.lock(),lock.tryLock()
		lock.lockInterruptibly();
		try{
			System.out.println("添加元素:"+item);
			++count;
			container.add(item);
			System.out.println("唤醒阻塞线程...");
			//添加成功之后,调用signal()发出唤醒通知
			//注意这里的唤醒一定要在unlock()之前
			conditionNull.signal();
			Thread.sleep(2000);
		}finally {
			System.out.println("添加方法释放锁...");
			//手动释放锁
			lock.unlock();
		}
	}

	/**
	 * 从队列中后去数据
	 * @return
	 * @throws InterruptedException
	 */
	public Integer take() throws InterruptedException {
		lock.lockInterruptibly();
		try {
			try {
				while (count == 0){
					System.out.println("队列元素为空,进入阻塞...");
					conditionNull.await();
				}
			} catch (InterruptedException ie) {
				System.out.println("出现异常,唤醒阻塞线程conditionNull");
				conditionNull.signal();
				throw ie;
			}

			--count;
			Integer x = container.get(0);
			System.out.println("取出方法取出元素:"+x);
			conditionFull.signal();
			return x;

		} finally {
			System.out.println("取出方法释放锁...");
			lock.unlock();
		}
	}
}

8.第八步,测试

public static void main(String[] args) {
		SimpleBlockingQueue sbq = new SimpleBlockingQueue();
		Thread t1 = new Thread(){
			public void run() {
				try {
					sbq.add(1);
					Thread.sleep(1000);
					sbq.add(2);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		};
		Thread t2 = new Thread(){
			public void run() {
				try {
					while (true){
						Integer item = sbq.take();
					}
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		};

		t2.start();
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t1.start();

	}
执行结果:
队列元素为空,进入阻塞...
添加元素:1
唤醒阻塞线程...
添加方法释放锁...
取出方法取出元素:1
取出方法释放锁...
队列元素为空,进入阻塞...
添加元素:2
唤醒阻塞线程...
添加方法释放锁...
取出方法取出元素:1
取出方法释放锁...
队列元素为空,进入阻塞...

到此,一个简单的阻塞队列就完成了。

这里只是简单的实现了队列的存取和阻塞唤醒,很多的细节问题并没有考虑。所以生产环境慎用,仅作为理解阻塞队列原理的小练习。

  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值