并行设计模式

目录

1.Future模式

2.Master-Worker模式

3.生产者-消费者

4.fork/join框架

4.1 单线程问题

4.2 fork-join


并行设计模式属于设计优化的一部分,它是对一些常用的多线程结构的总结和抽象。与串行程序相比,并行程序的结构更加的复杂。因此合理的使用并行模式在多线程开发中更具有意义。在这里主要介绍Future、Master-Worker和生产者-消费者模型。

1.Future模式

Future模式有点类似于商品订单。比如在网购时,当我们看重某一件商品的时候,就可以提交订单,当订单处理完成之后,在家里等待商品送货上门即可。或者说更形象的我们发送Ajax请求的时候,页面是异步的进行后台处理,用户无须一直等待请求的结果,可以继续浏览或操作其他内容。该设计模式在JDK中有具体的实现,可以直接去使用。Future设计模式的实例代码如下:

首先定义一个获取数据的接口Data:

public interface Data {
    String getRequest();
}

定义实际处理任务的类RealData

public class RealData implements Data {

	private String result;

	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;
	}

}

定义FutureData,负责将RealData处理的结果装载返回:


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();
	}
	
	@Override
	public synchronized String getRequest() {
		//如果没装载好 程序就一直处于阻塞状态
		while(!isReady){
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		//装载好直接获取数据即可
		return this.realData.getRequest();
	}


}

定义FutureClient类,暴露给用户去请求使用:

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;
	}
	
}

测试类Main:

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("做其他的事情...");
		
		String result = data.getRequest();
		System.out.println(result);
		
	}
}

执行的输出结果如下:

请求发送成功!
做其他的事情...
根据请求参数进行查询,这是一个很耗时的操作..
操作完毕,获取结果
查询结果

2.Master-Worker模式

Master-Worker模式是常用的并行计算模式。它的核心思想是系统由两类进程协作工作:Master进程和Worker进程。Master负责接收和分配任务,Worker负责处理子任务。当各个Worker子进程处理完成后,会将结果返回给Master,由Master做归纳和总结。其好处是能将一个大任务分解成若干个小任务,并行执行,从而提高系统的吞吐量。Master-Worker模式的实现代码如下:

定义Task类:

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;
	} 
	
}

 定义Worker类,负责处理Task任务:

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;
    }
    
}

定义Master类,负责分发任务和对Worker处理的结果归并:

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 计算结果方法
    public int getResult() {
        int priceResult = 0;
        for (Map.Entry<String, Object> me : resultMap.entrySet()) {
            priceResult += (Integer) me.getValue();
        }
        return priceResult;
    }

}

测试类Main.class

import java.util.Random;

public class Main {

    public static void main(String[] args) {
        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;
            }
        }

    }
}

输出的结果为:

最终结果:50921, 执行时间:2537

3.生产者-消费者

生产者和消费者也是一个非常经典的多线程模式,我们在实际开发中应用非常广泛的思想理念。在生产-消费模式中,通常有两类线程,即若干个生产者的线程和若干个消费者的线程。生产者线程负责提交用户请求,消费者线程则负责具体处理生产者提交的任务,在生产者和消费者之间通过共享内存缓存区进行通信。

定义处理的数据Data:

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 + "}";
	}
	
}

定义Privider,负责生产任务:

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;
	}
	
}

定义Consumer,负责取任务:

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();
			}
		}
	}
}

测试类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();
		

	}
	
}

4.fork/join框架

假如表里有个一亿的商品,我们来统计一下商品个数。这里首先模拟一个数据库的操作,代码如下:

package com.martin.learn.concurrent.forkjoin;

import java.util.concurrent.ThreadLocalRandom;

/**
 * @author: martin
 * @date: 2020/2/21
 */
public class DBUtils {
    private static final ThreadLocalRandom random = ThreadLocalRandom.current();

    public static int getMin() {
        return random.nextInt(10);
    }

    public static int getMax() {
        return 100000000;
    }

    /**
     * 模拟计算商品记录数
     *
     * @param startId 开始索引
     * @param endId   结束索引
     * @return
     */
    public static int sumRecorders(int startId, int endId) {
        try {
            //模拟查询数据库的操作 耗时5ms
            Thread.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return endId - startId;
    }
}

4.1 单线程问题

假如我们使用单线程来统计商品的个数:

package com.martin.learn.concurrent.forkjoin;

/**
 * @author: martin
 * @date: 2020/2/21
 */
public class SingleThreadTest {
    /**
     * 通常数据库一次查询的记录数最多大概5000行,不要太多
     */
    private final static int THRESHOLD = 5000;

    private static void sum() {
        int start = DBUtils.getMin();
        int end = DBUtils.getMax();
        int sum = 0;

        long startTime = System.currentTimeMillis();
        while (start < end) {
            sum += DBUtils.sumRecorders(start, start + THRESHOLD);
            start = start + THRESHOLD + 1;
        }
        long endTime = System.currentTimeMillis();

        System.out.println("sum:" + sum + ",cost:" + (endTime - startTime) / 1000 + "s");
    }

    public static void main(String[] args) {
        sum();
    }
}

代码输出如下:

sum:99980000,cost:112s

执行统计完成大概112s,时间比较长,我们可以通过fork/join框架做一下优化。

4.2 fork-join

fork-join可以充分利用多核CPU的优势,把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行。fork-join的思想是分而治之,示意图如下:

fork-join框架Java执行的核心类是ForkJoinPool,实例代码如下:

package com.martin.learn.concurrent.forkjoin;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

/**
 * @author: martin
 * @date: 2020/2/21
 */
public class SumTask extends RecursiveTask<Long> {
    private static final int THRESHOLD = 5000;

    private int startId;
    private int endId;

    public SumTask(int startId, int endId) {
        this.startId = startId;
        this.endId = endId;
    }

    @Override
    protected Long compute() {
        if (endId - startId <= THRESHOLD) {
            return Long.valueOf(DBUtils.sumRecorders(startId, endId));
        } else {
            int mid = (startId + endId) / 2;
            SumTask leftTask = new SumTask(startId, mid);
            SumTask rightTask = new SumTask(mid + 1, endId);
            invokeAll(leftTask, rightTask);

            long leftJoin = leftTask.join();
            long rightJoin = rightTask.join();
            return leftJoin + rightJoin;
        }
    }

    public static void main(String[] args) {
        //指定并行的数据
        ForkJoinPool forkJoinPool = new ForkJoinPool(100);
        long startTime = System.currentTimeMillis();
        int start = DBUtils.getMin();
        int end = DBUtils.getMax();

        SumTask sumTask = new SumTask(start,end);
        forkJoinPool.invoke(sumTask); //拆分计算,调用invoke,实际执行的是compute()
        System.out.println("sum:" + sumTask.join() + ",cost:" + (System.currentTimeMillis() - startTime) / 1000 + "s");
    }
}

运行结果如下:

sum:99967230,cost:2s

这里我们可以看到执行的时间大大地缩短。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值