注:什么叫线程安全?这个首先要明确。线程安全的类 ,指的是类内共享的全局变量的访问必须保证是不受多线程形式影响的。如果由于多线程的访问(比如修改、遍历、查看)而使这些变量结构被破坏或者针对这些变量操作的原子性被破坏,则这个类就不是线程安全的。
今天就聊聊这两种Queue,本文分为以下两个部分,用分割线分开:
-
BlockingQueue 阻塞算法ConcurrentLinkedQueue,非阻塞算法
首先来看看BlockingQueue:
Queue是什么就不需要多说了吧,一句话:队列是先进先出。相对的,栈是后进先出。如果不熟悉的话先找本基础的数据结构的书看看吧。
BlockingQueue,顾名思义,“阻塞队列”:可以提供阻塞功能的队列。
首先,看看BlockingQueue提供的常用方法:
| 可能报异常 | 返回布尔值 | 可能阻塞 | 设定等待时间 | |
| 入队 | add(e) | offer(e) | put(e) | offer(e, timeout, unit) |
| 出队 | remove() | poll() | take() | poll(timeout, unit) |
| 查看 | element() | peek() | 无 | 无 |
从上表可以很明显看出每个方法的作用,这个不用多说。我想说的是:
-
add(e) remove() element() 方法不会阻塞线程。当不满足约束条件时,会抛出IllegalStateException 异常。例如:当队列被元素填满后,再调用add(e),则会抛出异常。offer(e) poll() peek() 方法即不会阻塞线程,也不会抛出异常。例如:当队列被元素填满后,再调用offer(e),则不会插入元素,函数返回false。要想要实现阻塞功能,需要调用put(e) take() 方法。当不满足约束条件时,会阻塞线程。
好,上点 源码你就更明白了。以ArrayBlockingQueue类为例:
对于第一类方法,很明显如果操作不成功就抛异常。而且可以看到其实调用的是第二类的方法,为什么?因为第二类方法返回boolean啊。
Java代码
-
public boolean add(E e) {if (offer(e))return true;elsethrow new IllegalStateException("Queue full");//队列已满,抛异常}
public E remove() {E x = poll();if (x != null)return x;elsethrow new NoSuchElementException();//队列为空,抛异常}
注:先不看阻塞与否,这ReentrantLock的使用方式就能说明这个类是线程安全类。
Java代码
-
public boolean offer(E e) {if (e == null)throw new NullPointerException();final ReentrantLock lock = this.lock;lock.lock();try {if (count == items.length)//队列已满,返回falsereturn false;else {insert(e);//insert方法中发出了notEmpty.signal();return true;}} finally {lock.unlock();}}
public E poll() {final ReentrantLock lock = this.lock;lock.lock();try {if (count == 0)//队列为空,返回falsereturn null;E x = extract();//extract方法中发出了notFull.signal();return x;} finally {lock.unlock();}}
对于第三类方法,这里面涉及到Condition类,简要提一下,
await方法指:造成当前线程在接到信号或被中断之前一直处于等待状态。
signal方法指:唤醒一个等待线程。
Java代码
-
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这个条件,这时当前线程被阻塞notFull.await();} catch (InterruptedException ie) {notFull.signal(); //唤醒受notFull阻塞的当前线程throw ie;}insert(e);} finally {lock.unlock();}}
public E take() throws InterruptedException {final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {try {while (count == 0)//如果队列为空,等待notEmpty这个条件,这时当前线程被阻塞notEmpty.await();} catch (InterruptedException ie) {notEmpty.signal();//唤醒受notEmpty阻塞的当前线程throw ie;}E x = extract();return x;} finally {lock.unlock();}}
第四类方法就是指在有必要时等待指定时间,就不详细说了。
再来看看BlockingQueue接口的具体实现类吧:
-
ArrayBlockingQueue,其构造函数必须带一个int参数来指明其大小LinkedBlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定PriorityBlockingQueue,其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数的Comparator决定的顺序
上面是用ArrayBlockingQueue举得例子,下面看看LinkedBlockingQueue:
首先,既然是链表,就应该有Node节点,它是一个内部静态类:
Java代码
-
static class Node