阻塞队列分析

阻塞队列分析

解决问题

  • 问题

    在concurrent包发布之前,程序员需要自己去控制线程的阻塞和唤醒,同时兼顾效率和线程安全,这给程序带来了不小的复杂度

  • 解决

    使用阻塞队列,BlockingQueue,减少代码复杂程度

优势劣势

  • 优势

    不需要关心什么时候阻塞线程,什么时候唤醒线程,BlockingQueue解决了这些问题

  • 劣势

    没有明显的缺点,根据不同的场景选用不同的阻塞队列即可

适用场景

  • 生产者-消费者模型
  • 线程池
  • 消息中间件

组成部分

阻塞队列

  1. 当阻塞队列是空时,从队列中获取元素的操作将会被阻塞
  2. 当阻塞队列是满时,往队列里添加元素的操作将会被阻塞

类型

  • ArrayBlockingQueue:由数组结构组成的有界阻塞队列
  • LinkedBlockingQueue:由链表结构组成的有界(初始容量为Integer.MAX_VALUE)阻塞队列
  • PriorityBlockingQueue:支持优先级排序的无界阻塞队列
  • DelayQueue:使用优先级队列实现的延迟无界阻塞队列
  • SynchronousQueue:不存储元素的队列,也即单个元素队列
  • LinkedTransferQueue:由链表结构组成的无界阻塞队列
  • LinkedBlockingDeque:由链表结构组成的双向阻塞队列

核心方法

Throws Exception特殊值阻塞超时
Insertadd(o)offer(o)put(o)offer(o, timeout, timeunit)
Removeremove(o)poll()take()poll(timeout, timeunit)
Examineelement()peek()
  1. Throws Exception: 如果尝试的操作不可能立即发生,则抛出一个异常。
  2. 特殊值:如果尝试的操作不能立即执行,则会返回一个特殊值(通常为true / false)。
  3. 阻塞:如果尝试的操作不可能立即执行,那么该方法将阻塞。
  4. 超时:如果尝试的操作不可能立即执行,则该方法调用将阻塞,但不会超过给定的超时。
    返回一个特殊值,告诉操作是否成功(通常为true / false)。

无法将null插入到BlockingQueue中。如果尝试插入null,则BlockingQueue将引发NullPointerException。

底层原理

​ 前面谈到了非阻塞队列和阻塞队列中常用的方法,下面来探讨阻塞队列的实现原理,本文以ArrayBlockingQueue为例,其他阻塞队列实现原理可能和ArrayBlockingQueue有一些差别,但是大体思路应该类似,有兴趣的朋友可自行查看其他阻塞队列的实现源码。

​ 首先看一下ArrayBlockingQueue类中的几个成员变量:

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
 
private static final long serialVersionUID = -817911632652898426L;
 
/** The queued items  */
private final E[] items;
/** items index for next take, poll or remove */
private int takeIndex;
/** items index for next put, offer, or add. */
private int putIndex;
/** Number of items in the queue */
private int count;
 
/*
* Concurrency control uses the classic two-condition algorithm
* found in any textbook.
*/
 
/** Main lock guarding all access */
private final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
}

​ 可以看出,ArrayBlockingQueue中用来存储元素的实际上是一个数组,takeIndex和putIndex分别表示队首元素和队尾元素的下标,count表示队列中元素的个数。

​ lock是一个可重入锁,notEmpty和notFull是等待条件。

​ 下面看一下ArrayBlockingQueue的构造器,构造器有三个重载版本:

public ArrayBlockingQueue(int capacity) {
}
public ArrayBlockingQueue(int capacity, boolean fair) {
 
}
public ArrayBlockingQueue(int capacity, boolean fair,
                          Collection<? extends E> c) {
}

​ 第一个构造器只有一个参数用来指定容量,第二个构造器可以指定容量和公平性,第三个构造器可以指定容量、公平性以及用另外一个集合进行初始化。

​ 然后看它的两个关键方法的实现:put()和take():

public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    final E[] items = this.items;
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        try {
            while (count == items.length)
                notFull.await();
        } catch (InterruptedException ie) {
            notFull.signal(); // propagate to non-interrupted thread
            throw ie;
        }
        insert(e);
    } finally {
        lock.unlock();
    }
}

​ 从put方法的实现可以看出,它先获取了锁,并且获取的是可中断锁,然后判断当前元素个数是否等于数组的长度,如果相等,则调用notFull.await()进行等待,如果捕获到中断异常,则唤醒线程并抛出异常。

当被其他线程唤醒时,通过insert(e)方法插入元素,最后解锁。

我们看一下insert方法的实现:

private void insert(E x) {
    items[putIndex] = x;
    putIndex = inc(putIndex);
    ++count;
    notEmpty.signal();
}

​ 它是一个private方法,插入成功后,通过notEmpty唤醒正在等待取元素的线程。

下面是take()方法的实现:

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        try {
            while (count == 0)
                notEmpty.await();
        } catch (InterruptedException ie) {
            notEmpty.signal(); // propagate to non-interrupted thread
            throw ie;
        }
        E x = extract();
        return x;
    } finally {
        lock.unlock();
    }
}

跟put方法实现很类似,只不过put方法等待的是notFull信号,而take方法等待的是notEmpty信号。在take方法中,如果可以取元素,则通过extract方法取得元素,下面是extract方法的实现:

private E extract() {
    final E[] items = this.items;
    E x = items[takeIndex];
    items[takeIndex] = null;
    takeIndex = inc(takeIndex);
    --count;
    notFull.signal();
    return x;
}

跟insert方法也很类似。

其实从这里大家应该明白了阻塞队列的实现原理,事实它和我们用Object.wait()、Object.notify()和非阻塞队列实现生产者-消费者的思路类似,只不过它把这些工作一起集成到了阻塞队列中实现。

相似对比

非阻塞队列实现生产者-消费者模式(Object)

使用Object的wait/notify的消息通知机制

package top.ygy.thread.productor;

import java.util.ArrayList;
import java.util.List;

public class ConditionChange {
	private static List<String> lockObject = new ArrayList();

	public static void main(String[] args) {
		Consumer consumer1 = new Consumer(lockObject);
		Consumer consumer2 = new Consumer(lockObject);
		Productor productor = new Productor(lockObject);
		consumer1.start();
		consumer2.start();
		productor.start();
	}

	static class Consumer extends Thread {
		private List<String> lock;

		public Consumer(List lock) {
			this.lock = lock;
		}

		@Override
		public void run() {
			synchronized (lock) {
				try {
					// 这里使用if的话,就会存在wait条件变化造成程序错误的问题
					while (lock.isEmpty()) {
						System.out.println(
								Thread.currentThread().getName() + " list为空");
						System.out.println(
								Thread.currentThread().getName() + " 调用wait方法");
						lock.wait();
						System.out.println(Thread.currentThread().getName()
								+ "  wait方法结束");
					}
					String element = lock.remove(0);
					System.out.println(Thread.currentThread().getName()
							+ " 取出第一个元素为:" + element);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}

	}

	static class Productor extends Thread {
		private List<String> lock;

		public Productor(List lock) {
			this.lock = lock;
		}

		@Override
		public void run() {
			synchronized (lock) {
				System.out
						.println(Thread.currentThread().getName() + " 开始添加元素");
				lock.add(Thread.currentThread().getName());
				lock.notifyAll();
			}
		}

	}
}

非阻塞队列实现生产者-消费者模式(Lock)

使用Lock的Condition的await/signal的消息通知机制

package top.ygy.thread.productor;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Description: TODO(这里用一句话描述这个类的作用)
 * @author yangguangyuan
 * @date 2019年6月20日 题目:一个初始值为0的变量,两个线程对其交替操作,一个加1一个减1,来5轮
 * 
 *  1. 线程 操作 资源类 
 *	2. 判断 干活 通知
 */
public class ProdConsumer_TraditionDemo {
	public static void main(String[] args) {
		ShareData shareData = new ShareData();
		new Thread(() -> {
			for(int i=1;i<=5;i++) {
				shareData.increment();
			}
		},"AA").start();
		
		new Thread(() -> {
			for(int i=1;i<=5;i++) {
				shareData.decrement();
			}
		},"BB").start();
		
		new Thread(() -> {
			for(int i=1;i<=5;i++) {
				shareData.increment();
			}
		},"CC").start();
		
		new Thread(() -> {
			for(int i=1;i<=5;i++) {
				shareData.decrement();
			}
		},"DD").start();
	}
}

class ShareData {
	private int number = 0;
	private Lock lock = new ReentrantLock();
	private Condition condition = lock.newCondition();

	public void increment() {
		lock.lock();
		try {
			// 1.判断
			while (number != 0) {
				condition.await();
			}

			number++;
			System.out
					.println(Thread.currentThread().getName() + "\t" + number);
			condition.signalAll();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}

	public void decrement() {
		lock.lock();
		try {
			// 1.判断
			while (number == 0) {
				condition.await();
			}

			number--;
			System.out
					.println(Thread.currentThread().getName() + "\t" + number);
			condition.signalAll();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
}

阻塞队列实现的生产者-消费者模式(BlockingQueue)

使用BlockingQueue实现

package top.ygy.thread.productor;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Description: TODO(阻塞队列)
 * @author yangguangyuan
 * @date 2019年6月21日
 * 
 *       volatile/CAS/AtomicInteger/BlockingQueue/线程交互/原子引用
 */
public class ProdConsumer_BlockQueue {
	public static void main(String[] args) {
		MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));

		new Thread(() -> {
			System.out.println(Thread.currentThread().getName()+"\t 生产线程启动");
			try {
				myResource.myProd();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		},"Prod").start();
		
		new Thread(() -> {
			System.out.println(Thread.currentThread().getName()+"\t 消费线程启动");
			try {
				myResource.myConsumer();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		},"Consumer").start();
		
		try {
			TimeUnit.SECONDS.sleep(5);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		myResource.stop();
	}
}

class MyResource {
	// 默认开启进行生产+消费
	private volatile boolean FLAG = true;
	private AtomicInteger atomicInteger = new AtomicInteger();

	BlockingQueue<String> blockingQueue = null;

	public MyResource(BlockingQueue<String> blockingQueue) {
		this.blockingQueue = blockingQueue;
		System.out.println(blockingQueue.getClass().getName());
	}

	public void myProd() throws InterruptedException {
		String data = null;
		boolean retValue;
		while (FLAG) {
			data = atomicInteger.incrementAndGet() + "";
			retValue = blockingQueue.offer(data, 2, TimeUnit.SECONDS);

			if (retValue) {
				System.out.println(Thread.currentThread().getName() + "\t 插入队列"
						+ data + "成功");
			} else {
				System.out.println(Thread.currentThread().getName() + "\t 插入队列"
						+ data + "失败");
			}

			TimeUnit.SECONDS.sleep(1);
		}

		System.out.println();
		System.out.println();
		System.out.println("生产者停止,FLAG=false,生产动作结束");
	}

	public void myConsumer() throws InterruptedException {
		String result = null;
		while (FLAG) {
			result = blockingQueue.poll(2, TimeUnit.SECONDS);

			if (null == result || result.equalsIgnoreCase("")) {
				FLAG = false;
				System.out.println(
						Thread.currentThread().getName() + "\t 消费者停止,超时2s,消费退出");
				System.out.println();
				System.out.println();
				return;
			}

			System.out.println(Thread.currentThread().getName() + "\t 消费队列"
					+ result + "成功");
		}
		System.out.println("消费者停止,FLAG=false,消费动作结束");
	}

	public void stop() {
		FLAG = false;
	}
}

参考:

Java并发编程:阻塞队列

BlockingQueue 使用(生产者-消费者)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值