并行设计模式属于设计优化的一部分,它是对一些常用的多线程结果的总结和抽象。与串行程序相比,并行程序的结构通常更为复杂。因此合理的使用并行模式在多线程开发中更具有意义。
在这里主要介绍三种常用模式:
1. Future设计模式
2. Master-Worker
3. 生产者-消费者模型
1、Future设计模式(异步操作原理)
Future模式有点类似于商品订单。比如在网购时,当我们看到某一件商品时,就可以提交订单,当订单处理完成后,在家里等待商品送货上门即可,在送货这段时间我们不需要傻乎乎的在家一直等着,我们可以继续去做我们喜欢的时候,比如打球啊、旅游啊,送货的这一过程去别人帮我们干的,M天后快递到了我们直接去取就行。
或者说更形象的我们发送Ajax请求的时候,页面是异步的进行后台处理,用户无须一直等待请求的结果,可以继续浏览或者操作其他内容。
流程如下:
模拟示例代码:
1)、启动Future客户端
public class Main {
public static void main(String[] args) throws InterruptedException {
FutureClient fc = new FutureClient();
//异步去做操作
Data data = fc.request("请求参数");
//此处省略做其他事情的操作业务代码。意思就是上面的方法在执行的过程中完全不影响程序在做其他业务
System.out.println("请求发送成功!");
System.out.println("做其他的事情...");
//因为里面代码使用 wait() 和 notify() 实现,如果数据没有封装好,那么就会进行等待,如果已经封装好,那么数据就会直接返回。
String result = data.getRequest();
System.out.println(result);
}
}
2)、Future客户端
public class FutureClient {
public Data request(final String queryStr) {
//1 我想要一个代理对象(Data接口的实现类)先返回给发送请求的客户端,告诉他请求已经接收到,可以做其他的事情
final FutureData futureData = new FutureData();
//2 启动一个新的线程,去加载真实的数据,传递给这个代理对象
new Thread(new Runnable() {
@Override
public void run() {
//3 这个新的线程可以去慢慢的加载真实对象,然后传递给代理对象。模拟查询数据库业务......
RealData realData = new RealData(queryStr);
futureData.setRealData(realData);
}
}).start();
return futureData;
}
}
3)、FutureData包装类
/**
* @author lyl
*/
public class FutureData implements Data{
private RealData realData ;
private boolean isReady = false;
public synchronized void setRealData(RealData realData) {
//如果已经装载完毕了,就直接返回
if(isReady){
return;
}
//如果没装载,进行装载真实对象
this.realData = realData;
isReady = true;
//唤醒等待的程序,但是并没有把锁释放,notify方法是不释放锁的。
this.notify();
}
@Override
public synchronized String getRequest() {
//如果没装载好 程序就一直处于阻塞状态
while(!isReady){
try {
//等待唤醒
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//装载好直接获取数据即可
return this.realData.getRequest();
}
}
4)、RealData 数据,模拟一个数据库操作
public class RealData implements Data{
private String result ;
//这里模拟一个dao数据库操作
public RealData (String queryStr){
System.out.println("根据" + queryStr + "进行查询,这是一个很耗时的操作..");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("操作完毕,获取结果");
result = "查询结果";
}
@Override
public String getRequest() {
return result;
}
}
5)、业务接口
public interface Data {
String getRequest();
}
备注:项目实践使用不需要自己这样实现那么麻烦,可以直接实现并发包下的 java.util.concurrent.future 即可。但是Future异步操作原理必须得掌握。
2、Master-Worker设计模式
Master-Worker模式是常用的并行模式,主要作用就是就是对子任务进行归纳和总结
。它的核心思想是系统由两类进程协作工作:Master进程和Worker进程。Master负责接收和分配任务,Worker负责处理子任务
。
当各个Worker子进程处理完后,会将结果返回给Master,由Master做归纳和总结。其好处是能将一个大任务分解成若干个小任务,并执行,从而提高系统的吞吐量。
Master-Worker模式的工作模式如下图:
示列代码如下:
1)、启动线程
import java.util.Random;
public class Main {
public static void main(String[] args) {
//创建master,并且在master内部装置20个worker线程
Master master = new Master(new Worker(), 20);
Random r = new Random();
for(int i = 1; i <= 100; i++){
Task t = new Task();
t.setId(i);
t.setPrice(r.nextInt(1000));
master.submit(t);
}
master.execute();
long start = System.currentTimeMillis();
while(true){
if(master.isComplete()){
long end = System.currentTimeMillis() - start;
int priceResult = master.getResult();
System.out.println("最终结果:" + priceResult + ", 执行时间:" + end);
break;
}
}
}
}
2)、master对象
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
public class Master {
//1 有一个盛放任务的容器
private ConcurrentLinkedQueue<Task> workQueue = new ConcurrentLinkedQueue<Task>();
//2 需要有一个盛放worker的集合
private HashMap<String, Thread> workers = new HashMap<String, Thread>();
//3 需要有一个盛放每一个worker执行任务的结果集合
private ConcurrentHashMap<String, Object> resultMap = new ConcurrentHashMap<String, Object>();
//4 构造方法
public Master(Worker worker , int workerCount){
worker.setWorkQueue(this.workQueue);
worker.setResultMap(this.resultMap);
for(int i = 0; i < workerCount; i ++){
this.workers.put(Integer.toString(i), new Thread(worker));
}
}
//5 需要一个提交任务的方法
public void submit(Task task){
this.workQueue.add(task);
}
//6 需要有一个执行的方法,启动所有的worker方法去执行任务
public void execute(){
for(Map.Entry<String, Thread> me : workers.entrySet()){
me.getValue().start();
}
}
//7 判断是否运行结束的方法
public boolean isComplete() {
for(Map.Entry<String, Thread> me : workers.entrySet()){
if(me.getValue().getState() != Thread.State.TERMINATED){
return false;
}
}
return true;
}
//8 计算结果方法(Master核心要做的事情)
public int getResult() {
//汇总逻辑.......
int priceResult = 0;
for(Map.Entry<String, Object> me : resultMap.entrySet()){
priceResult += (Integer)me.getValue();
}
return priceResult;
}
}
3)、Worker线程对象
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
public class Worker implements Runnable {
private ConcurrentLinkedQueue<Task> workQueue;
private ConcurrentHashMap<String, Object> resultMap;
public void setWorkQueue(ConcurrentLinkedQueue<Task> workQueue) {
this.workQueue = workQueue;
}
public void setResultMap(ConcurrentHashMap<String, Object> resultMap) {
this.resultMap = resultMap;
}
@Override
public void run() {
while(true){
Task input = this.workQueue.poll();
if(input == null) break;
Object output = handle(input);
this.resultMap.put(Integer.toString(input.getId()), output);
}
}
private Object handle(Task input) {
Object output = null;
try {
//处理任务的耗时。。 比如说进行操作数据库。。。
Thread.sleep(500);
output = input.getPrice();
} catch (InterruptedException e) {
e.printStackTrace();
}
return output;
}
}
4)、Worker处理的任务对象
public class Task {
private int id;
private int price ;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
这种设计模式经常,当一个比较大的任务需要统计汇总时。如果只有一个线程去操作效率会比较低,那么就需要把他分配给不同的子项目去执行,然后主线程在子线程都执行完毕后进行汇总。
我在实践中,数据库做了分库,当时业务清单保存到了不同的8个库中。当时一个线程处理比较慢,后来就是开了8个线程对不同的库进行操作,然后主线程进行了汇总。
3、生产者-消费者模型
生产和消费者模式存在多种模式,一般分为
1、一个生产者,对应一个消费者
2、一个生产者,对应多个消费者
3、多个生产者,对应多一个消费者
4、多个生产者,对应多个消费者
实际开发中要根据业务的吞吐量,结合并行容器的特性,控制生产速度和消费的速度,做出正确的选择。
同时我们也会选择使用Disruptor
做高并发框架,这个框架很强大支持各种形状的生产消费模式,是一个环形结构的,又或者RabbitMQ、ActiveMQ、ZeroMQ、Kafka
等MQ中间件去做生产和消费。
1)、Main 线程入口
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
public class Main {
public static void main(String[] args) throws Exception {
//内存缓冲区
BlockingQueue<Data> queue = new LinkedBlockingQueue<Data>(10);
//生产者
Provider p1 = new Provider(queue);
Provider p2 = new Provider(queue);
Provider p3 = new Provider(queue);
//消费者
Consumer c1 = new Consumer(queue);
Consumer c2 = new Consumer(queue);
Consumer c3 = new Consumer(queue);
//创建线程池运行,这是一个缓存的线程池,可以创建无穷大的线程,没有任务的时候不创建线程。空闲线程存活时间为60s(默认值)
ExecutorService cachePool = Executors.newCachedThreadPool();
cachePool.execute(p1);
cachePool.execute(p2);
cachePool.execute(p3);
cachePool.execute(c1);
cachePool.execute(c2);
cachePool.execute(c3);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
p1.stop();
p2.stop();
p3.stop();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// cachePool.shutdown();
// cachePool.shutdownNow();
}
}
2)、生产者
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class Provider implements Runnable{
//共享缓存区
private BlockingQueue<Data> queue;
//多线程间是否启动变量,有强制从主内存中刷新的功能。即时返回线程的状态
private volatile boolean isRunning = true;
//id生成器
private static AtomicInteger count = new AtomicInteger();
//随机对象
private static Random r = new Random();
public Provider(BlockingQueue queue){
this.queue = queue;
}
@Override
public void run() {
while(isRunning){
try {
//随机休眠0 - 1000 毫秒 表示获取数据(产生数据的耗时)
Thread.sleep(r.nextInt(1000));
//获取的数据进行累计...
int id = count.incrementAndGet();
//比如通过一个getData方法获取了
Data data = new Data(Integer.toString(id), "数据" + id);
System.out.println("当前线程:" + Thread.currentThread().getName() + ", 获取了数据,id为:" + id + ", 进行装载到公共缓冲区中...");
if(!this.queue.offer(data, 2, TimeUnit.SECONDS)){
System.out.println("提交缓冲区数据失败....");
//do something... 比如重新提交
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void stop(){
this.isRunning = false;
}
}
3)、消费者
import java.util.Random;
import java.util.concurrent.BlockingQueue;
public class Consumer implements Runnable{
private BlockingQueue<Data> queue;
public Consumer(BlockingQueue queue){
this.queue = queue;
}
//随机对象
private static Random r = new Random();
@Override
public void run() {
while(true){
try {
//获取数据
Data data = this.queue.take();
//进行数据处理。休眠0 - 1000毫秒模拟耗时
Thread.sleep(r.nextInt(1000));
System.out.println("当前消费线程:" + Thread.currentThread().getName() + ", 消费成功,消费数据为id: " + data.getId());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
4)、数据类
public final class Data {
private String id;
private String name;
public Data(String id, String name){
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString(){
return "{id: " + id + ", name: " + name + "}";
}
}