1.方式一:使用BlockingQueue
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by zs on 2019/8/19.
*/
public class MySampleProduceAndConsumer {
BlockingQueue<Task> queue;
AtomicInteger taskNo = new AtomicInteger(0);
public MySampleProduceAndConsumer(int val){
queue = new LinkedBlockingQueue<>(val);
}
public Runnable getConsumer(){
return new Consumer();
}
public Runnable getProducer(){
return new Producer();
}
class Consumer implements Runnable{
@Override
public void run(){
//consume
try {
for (int i = 0; i < 50; i++) {
Task task = queue.take(); //关键代码1
System.out.println("cosume "+task.no);
Thread.sleep(5000);
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
class Producer implements Runnable{
@Override
public void run() {
//produce
try {
for (int i = 0; i < 50; i++) {
Thread.sleep((long)(1000*Math.random()));
Task task = new Task(taskNo.getAndIncrement());
queue.put(task); //关键代码2
System.out.println("produce " + task.no);
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public static void main(String[] args){
MySampleProduceAndConsumer mb = new MySampleProduceAndConsumer(3);
new Thread(mb.getConsumer()).start();
new Thread(mb.getProducer()).start();
}
}
需要注意:
//入队
queue.add(task); // 入队的时候,如果队列已满,则抛出异常
queue.offer(task);// 如果队列已满,则直接丢弃,还会不停生产,直到消费者消费一个,挪出一个空位置,才会添加进来,其余的全部丢弃了。
queue.put(task);// 入队的时候,如果队列已满,则阻塞,直到消费一个,添加进来,继续生产,继续阻塞。
//出队
Task task = queue.take();//如果队列为空,则一直阻塞
Task task = queue.remove();//删除成功返回true,否则返回false
Task task = queue.poll();//如果队列为空,返回NULL,否则返回元素
2.wait &¬ify 实现
如果不能直接只有阻塞队列,不能将并发与容量控制都封装在缓冲区中,就只能由消费者生产者内部完成,最简单的方法就是使用朴素的wait&¬ify实现线程同步。
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by zs on 2019/8/19.
*/
public class MyByWaitAndNotify {
private final Object BUFFER_LOCK = new Object(); //使用Object对象锁
Queue<Task> queue = new LinkedList<>(); //使用LinkedList自定义生产消费队列
int cap; //定义queue的大小
AtomicInteger taskNo = new AtomicInteger(0);
public MyByWaitAndNotify(int val){
this.cap = val;
}
//get方法返回内部类实例,Runnable实例
public Runnable getConsumer(){
return new Consumer();
}
public Runnable getProducer(){
return new Producer();
}
//内部类实现Consumer
private class Consumer implements Runnable{
@Override
public void run(){
//consume
try {
synchronized (BUFFER_LOCK){ //关键代码块1
while (queue.size()==0){
BUFFER_LOCK.wait();
}
Task task = queue.poll();
System.out.println("consumer "+task.no);
BUFFER_LOCK.notifyAll();
Thread.sleep(5000);
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
//内部类实现Producer
private class Producer implements Runnable{
@Override
public void run(){
try {
synchronized (BUFFER_LOCK){ //关键代码块2
Thread.sleep(900);
while (queue.size() == cap ){
BUFFER_LOCK.wait();
}
Task task = new Task(taskNo.getAndIncrement());
queue.offer(task);
System.out.println("producer "+task.no);
BUFFER_LOCK.notifyAll();
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
//测试类
public static void main(String[] args){
MyByWaitAndNotify my = new MyByWaitAndNotify(3);
for (int i = 0; i < 30; i++) {
new Thread(my.getProducer()).start();
}
for (int i = 0; i < 30; i++) {
new Thread(my.getConsumer()).start();
}
}
}
缺点:这种方式实现不灵活。
3. 简单的 LOCK&&Condition 实现
使用java.util.concurrent包下提供的 LOCK&& Condition
- 该思路和方法二完全相同,只不过将synchronized换成了 Lock而已。
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by zs on 2019/8/19.
*/
public class MyByLockAndCondition {
private final Lock BUFFER_LOCK = new ReentrantLock();
private final Condition BUFFER_CON = BUFFER_LOCK.newCondition();
private Queue<Task> buffer = new LinkedList<>();
private int cap;
private AtomicInteger taskNo = new AtomicInteger(0);
public MyByLockAndCondition(int val){
this.cap = val;
}
public Runnable getConsumer(){
return new Consumer();
}
public Runnable getProducer(){
return new Producer();
}
private class Consumer implements Runnable{
@Override
public void run(){
try {
consume();//消费者线程 消费一次
}catch (InterruptedException e){
e.printStackTrace();
}
}
//consume方法
public void consume() throws InterruptedException{
BUFFER_LOCK.lockInterruptibly(); //加锁,由于要捕捉InterruptedException异常,故把consume方法单独拿出来写
try {
while (buffer.size()==0){
BUFFER_CON.await();
}
Task task = buffer.poll();
Thread.sleep(1000);
System.out.println("consumer "+task.no);
BUFFER_CON.signalAll(); //唤醒线程,将等待队列线程放进同步队列
}catch (InterruptedException e){
e.printStackTrace();
}finally {
BUFFER_LOCK.unlock(); //释放锁放在finally块
}
}
}
private class Producer implements Runnable{
@Override
public void run(){
try{
produce();//生产者线程 生产一次
}catch(InterruptedException e){
e.printStackTrace();
}
}
public void produce() throws InterruptedException{
//produce
BUFFER_LOCK.lockInterruptibly();//加锁
try {
while (buffer.size()==cap){
BUFFER_CON.await();
}
Task task = new Task(taskNo.getAndIncrement());
System.out.println("produce "+task.no);
buffer.offer(task);
Thread.sleep(900);
BUFFER_CON.signalAll();
}finally{
BUFFER_LOCK.unlock();
}
}
}
//测试
public static void main(String[] args){
MyByLockAndCondition my = new MyByLockAndCondition(3);
for (int i = 0; i < 10; i++) {
new Thread(my.getConsumer()).start();
}
for (int i = 0; i < 10; i++) {
new Thread(my.getProducer()).start();
}
}
}
4.更高并发的lock&&Condition
前面的几种方法,会发现,方法1的并发性高于方法2和3,暂且不关心BlockingQueue的内部具体实现,现在来分析如何优化方法3.
-
分析:方法3的瓶颈很明显,方法3只用了一个BUFFER_LOCK,同一时刻只有一个线程能访问buffer(消费者线程或者生产者线程)。而实际上,如果缓冲区是一个队列的话,“生产者入队”和“消费者出队”两个操作并没有直接的关系,可以同时进行。可以在队尾入队,在队首出队,这样性能可以提高两三倍。
-
如何去掉这个瓶颈?需要两个锁:CONSUME_LOCK和PRODUCE_LOCK,相应地,也需要两个条件变量:NOT_EMPTY和NOT_FULL。这样消费者和生产者之间就可以并行。
public class LockConditionModel2 implements Model {
private final Lock CONSUME_LOCK = new ReentrantLock();
private final Condition NOT_EMPTY = CONSUME_LOCK.newCondition();
private final Lock PRODUCE_LOCK = new ReentrantLock();
private final Condition NOT_FULL = PRODUCE_LOCK.newCondition();
private final Buffer<Task> buffer = new Buffer<>();
private AtomicInteger bufLen = new AtomicInteger(0);
private final int cap;
private final AtomicInteger increTaskNo = new AtomicInteger(0);
public LockConditionModel2(int cap) {
this.cap = cap;
}
@Override
public Runnable newRunnableConsumer() {
return new ConsumerImpl();
}
@Override
public Runnable newRunnableProducer() {
return new ProducerImpl();
}
private class ConsumerImpl extends AbstractConsumer implements Consumer, Runnable {
@Override
public void consume() throws InterruptedException {
int newBufSize = -1;
CONSUME_LOCK.lockInterruptibly();
try {
while (bufLen.get() == 0) {
System.out.println("buffer is empty...");
NOT_EMPTY.await();
}
Task task = buffer.poll();
newBufSize = bufLen.decrementAndGet();
if (newBufSize > 0) {
NOT_EMPTY.signalAll();
}
} finally {
CONSUME_LOCK.unlock();
}
if (newBufSize < cap) {
PRODUCE_LOCK.lockInterruptibly();
try {
NOT_FULL.signalAll();
} finally {
PRODUCE_LOCK.unlock();
}
}
assert task != null;
// 固定时间范围的消费,模拟相对稳定的服务器处理过程
Thread.sleep(500 + (long) (Math.random() * 500));
System.out.println("consume: " + task.no);
}
}
private class ProducerImpl extends AbstractProducer implements Producer, Runnable {
@Override
public void produce() throws InterruptedException {
// 不定期生产,模拟随机的用户请求
Thread.sleep((long) (Math.random() * 1000));
int newBufSize = -1;
PRODUCE_LOCK.lockInterruptibly();
try {
while (bufLen.get() == cap) {
System.out.println("buffer is full...");
NOT_FULL.await();
}
Task task = new Task(increTaskNo.getAndIncrement());
buffer.offer(task);
newBufSize = bufLen.incrementAndGet();
System.out.println("produce: " + task.no);
if (newBufSize < cap) {
NOT_FULL.signalAll();
}
} finally {
PRODUCE_LOCK.unlock();
}
if (newBufSize > 0) {
CONSUME_LOCK.lockInterruptibly();
try {
NOT_EMPTY.signalAll();
} finally {
CONSUME_LOCK.unlock();
}
}
}
}
private static class Buffer<E> {
private Node head;
private Node tail;
Buffer() {
// dummy node
head = tail = new Node(null);
}
public void offer(E e) {
tail.next = new Node(e);
tail = tail.next;
}
public E poll() {
head = head.next;
E e = head.item;
head.item = null;
return e;
}
private class Node {
E item;
Node next;
Node(E item) {
this.item = item;
}
}
}
public static void main(String[] args) {
Model model = new LockConditionModel2(3);
for (int i = 0; i < 2; i++) {
new Thread(model.newRunnableConsumer()).start();
}
for (int i = 0; i < 5; i++) {
new Thread(model.newRunnableProducer()).start();
}
}
需要注意的是,由于需要同时在UnThreadSafe的缓冲区 buffer 上进行消费与生产,我们不能使用实现二、三中使用的队列了,需要自己实现一个简单的缓冲区 Buffer。Buffer要满足以下条件:
-
在头部出队,尾部入队
-
在poll()方法中只操作head
-
在offer()方法中只操作tail
这4种写法的本质相同——都是在使用或实现BlockingQueue。
实现一直接使用BlockingQueue,实现四实现了简单的BlockingQueue,而实现二、三则实现了退化版的BlockingQueue(性能降低一半)。
还能进一步优化吗
我们已经优化掉了消费者与生产者之间的瓶颈,还能进一步优化吗?
如果可以,必然是继续优化消费者与消费者(或生产者与生产者)之间的并发性能。然而,消费者与消费者之间必须是串行的,因此,并发模型上已经没有地方可以继续优化了
仔细分析下,实现四中的signalAll都可以换成signal。这里为了屏蔽复杂性,回避了这个优化。
不过在具体的业务场景中,一般还能够继续优化。如:
-
并发规模中等,可考虑使用CAS代替重入锁
-
模型上不能优化,但一个消费行为或许可以进一步拆解、优化,从而降低消费的延迟
-
一个队列的并发性能达到了极限,可采用“多个队列”(如分布式消息队列等)