1,使用wait()/notify()的方式
2,使用J.U.C下Condition的await()/signal()的方式实现
3,使用阻塞队列实现
注:这篇博文主要将使用阻塞队列实现,至于前面的两种可以看看我的另外一篇博客
1,一个支持两个附加操作的队列。这两个附加的操作支持阻塞的插入和移除方法。
2,Java针对阻塞队列的操作方式有不同的处理逻辑
1,推荐几篇博客:Java并发讲解
2,对于Java并发的学习,切不可知道几个关键字就可以了,要把J.U.C下的包逐个击破,了解其原理及其使用场景才是学习的关键。
1,ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列
2,LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
3,PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
4,DelayQueue:一个使用优先级队列实现的无界阻塞队列。
5,SynchronousQueue:一个不存储元素的阻塞队
6,LinkedTransferQueue:一个由链表结构组成的无界阻塞队列
7,LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列
总结,对于无界的队列,值得注意的是,在线程池的使用中,如果请求过多,就会造成OOM.很多队列都作为线程池的等待队列来使用,以满足不同的处理需求,具体看我推荐的博文
1,需求模型如下,该图来自《Java并发编程艺术》
2,具体实现并非copy(因为为不全)
3,简单理解:大致分三步
1),生产者生产数据,将数据放入阻塞队列,实际场景,来自客户段的请求之类的
2),消费者1从阻塞队列中拿取数据,当阻塞队列中没有数据时,阻塞队列就会调用LockSupport.park()使调用take()的线程阻塞,那消费者1 就会在那等着,如果遇到异常,该线程会被中断。
3),消费者1 的工作并非是处理这些数据,而是将他们分发给三个阻塞队列,根据得到的数据采用哈希算法,算出这条数据应该去那个队列,那条去那个队列。(不太清楚书上是怎么具体实现的,但是思想都是一样的,使这些数据均匀的分布到三个队列)
4)剩下的消费者就是真正处理数据 的了(这里我简但的做了个打印);
1,封装数据
package 多线程实战.生产者消费者;
/**
* 消息数据 待处理的数据
* 假设保存个人数据
*/
public class MessageData {
private String name;
private int id;
public MessageData(){
}
public MessageData(String name,int age){
this.id = age;
this.name = name;
}
public String getName() {
return name;
}
public int getId() {
return id;
}
public void setName(String name) {
this.name = name;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "["+name+"]";
}
}
2,封装阻塞队列,这里我使用的是ArrayBlockingQueue,也可以采用别的,效率可能会更好
package 多线程实战.生产者消费者;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/**
*
*/
public class MessageTaskQueue{
public final BlockingQueue queue;
public MessageTaskQueue(int size){
queue = new ArrayBlockingQueue<>(size);
}
public void put(MessageData data){
try {
queue.offer(data);
}catch (Exception e){
Thread.currentThread().interrupt();
}
}
public MessageData get(){
try {
return queue.take();
}catch (Exception e){
Thread.currentThread().interrupt();
}
return null;
}
}
3,创建生产者线程,用于生产数据并把数据放入阻塞队列
package 多线程实战.生产者消费者;
public class MessageProducer implements Runnable{
private MessageTaskQueue queue;
private MessageData data;
MessageProducer(MessageTaskQueue queue){
this.queue = queue;
}
@Override
public void run() {
for (;;){
data = new MessageData("LYY-"+System.currentTimeMillis()%9+"["+Thread.currentThread().getName()+"]",1);
System.out.println("生产消息···"+data.toString());
queue.put(data);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
这里的书写可能不太完美
4,创建消息分发的线程,我用一个hashmap保存者阻塞队列id和阻塞队列的关系,
package 多线程实战.生产者消费者;
import java.util.HashMap;
public class DispatchMessageTask implements Runnable{
private MessageTaskQueue queue;
private HashMap hashMap;
public DispatchMessageTask(MessageTaskQueue queue,HashMap hashMap){
this.hashMap = hashMap;
this.queue = queue;
}
@Override
public void run() {
for (;;){
try {
MessageData data = queue.get();
//这里就是所谓的哈希算法,System.nanoTime()这个函数产生的值
//就是哈希值,对size取模运算,会得到0,1,2这三个数,他们一一对应一个队列
int index = (int) (System.nanoTime() % hashMap.size());
MessageTaskQueue messageTaskQueue = hashMap.get(index);
System.out.println("消息分发···"+index+" 号队列获得消息: "+data.toString());
messageTaskQueue.put(data);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
5,创建消费者线程
package 多线程实战.生产者消费者;
public class MessageConsumer implements Runnable{
private MessageTaskQueue queue;
MessageConsumer(MessageTaskQueue queue){
this.queue=queue;
}
@Override
public void run() {
for (;;){
MessageData data = queue.get();
//拿到数据就对数据进行处理
System.out.println("消费消息···"+data.toString());
}
}
}
至于为什么是死循环,因为要一直拿数据,
6,启动类
package 多线程实战.生产者消费者;
import java.util.HashMap;
public class Run {
public static void main(String[] args) throws Exception{
//四个阻塞队列
MessageTaskQueue queue_1 = new MessageTaskQueue(100);
MessageTaskQueue queue_2 = new MessageTaskQueue(10);
MessageTaskQueue queue_3 = new MessageTaskQueue(10);
MessageTaskQueue queue_4 = new MessageTaskQueue(10);
//用于负载均衡
HashMap map = new HashMap<>();
map.put(0,queue_2);
map.put(1,queue_3);
map.put(2,queue_4);
//两个线程生产消息
MessageProducer producer = new MessageProducer(queue_1);
Thread producer_Thread_1 = new Thread(producer);
Thread producer_Thread_2 = new Thread(producer);
Thread producer_Thread_3 = new Thread(producer);
//消息分发
DispatchMessageTask dispatchMessageTask = new DispatchMessageTask(queue_1,map);
Thread dispach_Thread = new Thread(dispatchMessageTask);
//消费消息
MessageConsumer consumer = new MessageConsumer(queue_2);
MessageConsumer consumer1 = new MessageConsumer(queue_3);
MessageConsumer consumer2 = new MessageConsumer(queue_4);
Thread c1_Thread = new Thread(consumer);
Thread c2_Thread = new Thread(consumer1);
Thread c3_Thread = new Thread(consumer2);
//开启线程
producer_Thread_1.start();
producer_Thread_2.start();
producer_Thread_3.start();
Thread.sleep(1000);
dispach_Thread.start();
c1_Thread.start();
c2_Thread.start();
c3_Thread.start();
}
}
用两个线程来生产,数据还是很快被分发消费,自己实现可多加几个,或者把休眠时间调短一点,
这是一个全过程,可以适当调整阻塞队列的大小,观察队列阻塞的情况。。
参看:《Java并发编程艺术》