Java 5添加了并发编程java.util.concurrent包。JUC大体结构:
首先介绍并发集合包,这个包包含了一系列能够让 Java 并发编程变得更加简单轻松的类。
BlockingQueue接口表示一个线程存放和提取实例的队列,通常用于一个线程生产对象,而另外一个线程消费这些对象的场景。下图是对这个原理的阐述:
一个线程将会持续生产新对象并将其插入到队列之中,直到队列达到它所能容纳的临界点。如果该阻塞队列到达了其临界点,负责生产的线程将会在往里面插入新对象时会发生阻塞。它会一直处于阻塞之中,直到负责消费的线程从队列中拿走一个对象。如果消费线程尝试去从一个空的队列中提取对象的话,这个消费线程将会处于阻塞之中,直到一个生产线程把一个对象丢进队列。
BlockingQueue具有4组不同的方法用于插入、移除以及对队列中的元素进行检查。如果请求的操作不能得到立即执行的话,每个方法的表现也不同。这些方法如下:
% | 抛异常 | 特定值 | 阻塞 | 超时 |
---|---|---|---|---|
插入 | add(o) | offer(o) | put(o) | offer(o, timeout, timeunit) |
移除 | remove(o) | poll(o) | take(o) | poll(timeout, timeunit) |
检查 | element(o) | peek(o) |
四种方式解释:
// 抛异常
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
实现BlockingQueue接口的几个类:
接下来我们详细介绍下各个实现类的使用。
1. 数组阻塞队列 ArrayBlockingQueue
ArrayBlockingQueue是一个有界的阻塞队列,其内部实现是将对象放到一个数组里。ArrayBlockingQueue可以通过ReentrantLock设置公平锁,默认为false,如果设置fair为true则队列以FIFO(先进先出)的顺序对元素进行存储。
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
以下是在使用ArrayBlockingQueue的一个示例:
```java
public class BlockingQueueExample {
public static void main(String[] args) throws Exception {
BlockingQueue queue = new ArrayBlockingQueue(1024);
Producer producer = new Producer(queue);
Consumer consumer = new Consumer(queue);
new Thread(producer).start();
new Thread(consumer).start();
Thread.sleep(4000);
}
}
/**
* 生产者线程
*/
class Producer implements Runnable {
protected BlockingQueue queue = null;
public Producer(BlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
try {
queue.put("1");
Thread.sleep(1000);
queue.put("2");
Thread.sleep(1000);
queue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 消费者线程
*/
class Consumer implements Runnable {
protected BlockingQueue queue = null;
public Consumer(BlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
try {
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println(queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2. 延迟队列 DelayQueue
DelayQueue对元素进行持有直到一个特定的延迟到期,注入其中的元素必须实现java.util.concurrent.Delayed接口,接口定义如下:
public interface Delayed extends Comparable<Delayed> {
public long getDelay(TimeUnit timeUnit);
}
Delayed接口继承了Comparable接口,这也就意味着Delayed对象之间可以进行对比。这个可能在对DelayeQueue队列中的元素进行排序时有用,因此它们可以根据过期时间进行有序释放。
DelayQueue将会在每个元素的getDelay()方法返回的值的时间段之后才释放掉该元素。如果返回的是0或者负值,延迟将被认为过期,该元素将会在DelayQueue的下一次take被调用的时候被释放掉。
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
E first = q.peek();
if (first == null)
available.await();
else {
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
return q.poll();
first = null; // don't retain ref while waiting
if (leader != null)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && q.peek() != null)
available.signal();
lock.unlock();
}
}
ReentrantLock.lockInterruptibly()允许在等待时由其它线程调用等待线程的Thread.interrupt方法来中断等待线程的等待而直接返回,这时不用获取锁,而会抛出一个InterruptedException。
传递给getDelay()方法的参数是一个枚举型,它表明了将要延迟的时间段。在上一段代码中first.getDelay(NANOSECONDS)中的参数NANOSECONDS就是枚举型TimeUnit。
以下是使用DelayQueue的例子:
public class DelayQueueExample {
public static void main(String[] args) {
DelayQueue<DelayTask> queue = new DelayQueue<>();
queue.add(new DelayTask("1", 1000L, TimeUnit.MILLISECONDS));
queue.add(new DelayTask("2", 2000L, TimeUnit.MILLISECONDS));
queue.add(new DelayTask("3", 3000L, TimeUnit.MILLISECONDS));
System.out.println("queue put done");
while (!queue.isEmpty()) {
try {
DelayTask task = queue.take();
System.out.println(task.name + ":" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class DelayTask implements Delayed {
public String name;
public Long delayTime;
public TimeUnit delayTimeUnit;
public Long executeTime;
DelayTask(String name, long delayTime, TimeUnit delayTimeUnit) {
this.name = name;
this.delayTime = delayTime;
this.delayTimeUnit = delayTimeUnit;
this.executeTime = System.currentTimeMillis() + delayTimeUnit.toMillis(delayTime);
}
@Override
public int compareTo(Delayed o) {
if (this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS)) {
return 1;
} else if (this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS)) {
return -1;
}
return 0;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(executeTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
}
3. 链阻塞队列 LinkedBlockingQueue
LinkedBlockingQueue内部以一个链式结构(链接节点)对其元素进行存储。默认以Integer.MAX_VALUE作为上限。
LinkedBlockingQueue常用于生产者/消费者模式中,使用方式同ArrayBlockingQueue。
public class LinkedBlockingQueueExample {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> unbounded = new LinkedBlockingQueue<String>();
BlockingQueue<String> bounded = new LinkedBlockingQueue<String>(1024);
bounded.put("Value");
System.out.println(bounded.take());
}
}
4. 具有优先级的阻塞队列 PriorityBlockingQueue
PriorityBlockingQueue是一个基于优先级堆的无界的并发安全的优先级队列(FIFO)。
PriorityBlockingQueue通过内部组合PriorityQueue的方式实现优先级队列(private final PriorityQueue q;),另外在外层通过ReentrantLock实现线程安全,同时通过Condition实现阻塞唤醒。
基本原理:PriorityBlockingQueue通过使用堆这种数据结构实现将队列中的元素按照某种排序规则进行排序,从而改变先进先出的队列顺序,提供开发者改变队列中元素的顺序的能力。队列中的元素必须是可比较的,即实现Comparable接口,或者在构建函数时提供可对队列元素进行比较的Comparator对象。
PriorityBlockingQueue使用方式如下:
public class PriorityBlockingQueueExample {
public static PriorityBlockingQueue<User> queue = new PriorityBlockingQueue<User>();
public static void main(String[] args) {
queue.add(new User(1, "qlq1"));
queue.add(new User(5, "qlq2"));
queue.add(new User(23, "qlq3"));
queue.add(new User(55, "qlq4"));
queue.add(new User(9, "qlq5"));
queue.add(new User(3, "qlq6"));
for (User user : queue) {
try {
System.out.println(queue.take().name);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class User implements Comparable<User> {
int age;
String name;
public User(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public int compareTo(User o) {
return this.age > o.age ? -1 : 1;
}
}
//输出:
qlq4
qlq3
qlq5
qlq2
qlq6
qlq1
5. 同步队列 SynchronousQueue
SynchronousQueue是一个特殊的队列,它的内部只能容纳单个元素。如果该队列已有一个元素的话,试图向队列中插入一个新元素的线程将会阻塞,直到另一个线程将该元素从队列中取走。同样,如果该队列为空,试图向队列中抽取一个元素的线程将会阻塞,直到另一个线程向队列中插入了一条新的元素。
基本原理:SynchronousQueue通过将入栈出栈的线程绑定到队列的节点上,并借助LockSupport的park()和unpark()实现等待,先到达的线程A需调用LockSupport的park()方法将当前线程进入阻塞状态,直到另一个与之匹配的线程B调用LockSupport.unpark(Thread)来唤醒在该节点上等待的线程A。
可以通过以下方式使用SynchronousQueue:
public class SynchronousQueueExample {
public static void main(String[] args) throws InterruptedException {
final SynchronousQueue<Integer> queue = new SynchronousQueue<Integer>();
Thread putThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("put thread start");
try {
queue.put(1);
} catch (InterruptedException e) {
}
System.out.println("put thread end");
}
});
Thread takeThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("take thread start");
try {
System.out.println("take from putThread: " + queue.take());
} catch (InterruptedException e) {
}
System.out.println("take thread end");
}
});
putThread.start();
Thread.sleep(1000);
takeThread.start();
}
}
//输出
put thread start
take thread start
put thread end
take from putThread: 1
take thread end
后续更新。