阻塞队列的工作原理图如下
当阻塞队列是空时,从队列中获取元素的操作会被阻塞
当阻塞队列是满时,从队列中添加元素的操作会被阻塞
首先在多线程领域下,阻塞是指某些情况下会挂起线程,一旦条件满足,被挂起的线程又会自动被唤醒
使用了阻塞队列后
我们不需要手动的去唤醒,和阻塞,控制这些小细节 待会下面的生产者-消费者代码会带你感受其中的便捷
ArrayBlockingQueue :由数组结构组成的有界阻塞队列
LinkedBlockingQueue :由链表结构组成的有界(大小默认值为Integer.MAX_VALUE)阻塞队列
SynchronousQueue :不存储元素的阻塞队列,也即单个元素的队列 (每put 一个后必须消费take一个才能继续put)
/**
* SynchronousQueue案例
*/
public class SynchronousQueueDemo {
public static void main (String[] args) {
SynchronousQueue< String > blockingQueue = new SynchronousQueue<>();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "\t put 1.");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName() + "\t put 2.");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName() + "\t put 3.");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(() -> {
try {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "\t take" + blockingQueue.take());
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "\t take" + blockingQueue.take());
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "\t take" + blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
}
}
可以用在生产者消费者模式 ;线程池 ; 消息中间件中
采用synchronized实现
package com.demo.ProducerConsumerModel;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 生产者消费者模式实现
* 1、synchronized实现
*/
public class SynchronizedDemo {
public static void main (String[] args) {
SynchronizedCache synchronizedCache = new SynchronizedCache();
//3个线程去生产
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
synchronizedCache.producer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
//3个线程在消费
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
synchronizedCache.consumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
/**
* 资源类
*/
class SynchronizedCache {
/**
* 数据
*/
private AtomicInteger data = new AtomicInteger(0);
private Object object = new Object();
/**
* 生产者
*
* @throws InterruptedException
*/
public void producer () throws InterruptedException {
synchronized(object){
//防止虚假唤醒,所有的唤醒操作都应该放在while 目的是为了唤醒后 再判断一次
while (data.get() != 0) {
//等待 停止生产
object.wait();
}
//开始生产
data.getAndIncrement();
System.out.println(Thread.currentThread().getName() + "生产了数据");
//唤醒阻塞的线程
object.notify();
}
}
public void consumer () throws InterruptedException {
synchronized(object){
while (data.get() == 0) {
//等待 停止消费
object.wait();
}
//开始消费
data.getAndDecrement();
System.out.println(Thread.currentThread().getName() + "消费了数据");
//唤醒阻塞的线程
object.notify();
}
}
}
结果
Lock版本 可以通过condition 进行精确唤醒
package com.demo.ProducerConsumerModel;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 生产者消费者模式实现
* 2、Lock
*/
public class LockDemo {
public static void main (String[] args) {
LockCache lockCache = new LockCache();
//3个线程去生产
for (int i = 0; i < 3; i++) {
new Thread(() -> {
lockCache.producer();
}).start();
}
//3个线程在消费
for (int i = 0; i < 3; i++) {
new Thread(() -> {
lockCache.consumer();
}).start();
}
}
}
/**
* Lock资源类
*/
class LockCache {
/**
* 数据
*/
private AtomicInteger data = new AtomicInteger(0);
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
/**
* 生产者
*/
public void producer () {
lock.lock();
try {
while (data.get() != 0) {
condition.await();
}
//开始生产数据
data.getAndIncrement();
System.out.println(Thread.currentThread().getName() + "生产了数据");
//唤醒其他线程
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/**
* 消费者
*/
public void consumer () {
lock.lock();
try {
while (data.get() == 0) {
condition.await();
}
//开始消费数据
data.getAndDecrement();
System.out.println(Thread.currentThread().getName() + "消费了数据");
//唤醒其他线程
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
阻塞队列版本
package com.demo.ProducerConsumerModel;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/**
* 生产者消费者模式实现
* 3、阻塞队列实现
*/
public class BlockingQueueDemo {
public static void main (String[] args) {
BlockingQueueCache blockingQueueCache = new BlockingQueueCache();
for (int i = 0; i < 5; i++) {
new Thread(()->{
try {
blockingQueueCache.producer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
for (int i = 0; i < 5; i++) {
new Thread(()->{
try {
blockingQueueCache.consumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
/**
* 资源类
*/
class BlockingQueueCache {
private BlockingQueue blockingQueue = new ArrayBlockingQueue(1);
/**
* 生产者
*/
public void producer () throws InterruptedException {
//生产一个数据
blockingQueue.put(1);
System.out.println(Thread.currentThread().getName() + "生产了一个数据");
}
/**
* 消费者
*/
public void consumer () throws InterruptedException {
//生产一个数据
blockingQueue.take();
System.out.println(Thread.currentThread().getName() + "消费了一个数据");
}
}
三个版本对比 可以看出用了阻塞队列 可以帮助程序员少做一些细节的判断,简化了开发
线程池部分主要放在下一篇博客讲解,这里讲一下 Callable 怎么实现多线程
package com.demo;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 实现多线程的方式:实现Callable接口
*/
public class CallableDemo {
public static void main (String[] args) throws ExecutionException, InterruptedException {
//将Callable包装到FutureTask中
FutureTask<Integer> futureTask = new FutureTask<>(new MyThread());
new Thread(futureTask, "A").start();
new Thread(futureTask, "B").start(); //如果是同一个task,即使多个线程,也只进call()一次
System.out.println(Thread.currentThread().getName() + "...");
Integer result = futureTask.get(); //获取callable线程的计算结果,如果没有完成计算,会导致堵塞,直到计算完成
System.out.println("result:" + result);
}
}
class MyThread implements Callable {
@Override
public Object call () throws Exception {
System.out.println("Callable .....");
return 1024;
}
}