第4章 并行程序开发与优化
本章主要介绍基于Java的并行程序开发及其优化方法。对于多核CPU,传统的串行程序已经无法很好发挥CPU的性能。此时,就需要通过使用多线程并行的方式挖掘CPU的潜能。本章涉及的主要知识点有:
□常用的多线程设计模式,Future模式,Master-Worker模式,Guarded Suspeionsion模式,不变模式和生产者-消费者模式;
□JDK内置的多线程框架和各种线程池;
□JDK内置的并发数据结构;
□Java的并发控制方式,如内部锁,重入锁,读写锁,TreadLocal变量,信号量等;
□有关“锁”的一些优化方法;
□使用无锁的方式提升高并发程序的性能;
□使用轻量级的协程获得更高的并行度。
4.1 并行程序设计模式
并行设计模式属于设计优化的一部分,它是对一些常用的多线程结构的总结和抽象。与串行相比,并行程序的结构通常更加复杂。因此, 合理地使用并行模式在多线程开发中,更具有积极意义。本章主要介绍Future模式,Master-Worker模式,Guarded Suspeionsion模式,不变模式和生产者-消费者模式。
4.1.1 Future模式
Future模式有点类似商品订单。比如在网上购物时,当看中某一件商品时,就可以提交订单。当订单处理完毕后,便可在家里等待商品送货上门。卖家根据订单从仓库里取货,并配送到客户手上。在大部分情况下,商家对订单的处理并不那么快,有时甚至需要几天时间。而在这段时间内,客户完全不必傻傻地在家里等候,可以出门处理其它事物。
将此例类推到程序设计中,当某一段程序提交了一个请求,期望得到一个回复。但非常不幸的是,服务程序对这个请求的处理可能会很慢,比如请求可能是通过互联网,HTTP或者Web Service等并不高效的方式调用的。在传统的单线程环境下,调用函数同步的,也就是说它必须等到服务程序返回结果后,才能进行其它处理。而在Future模式下,调用方式改为异步,而原先等待返回的时间段,在主调用函数中,则可用于处理其它的事务。由于本身需要很长的一段时间来处理程序。但是,服务程序不等数据处理完成便立即返回客户端一个伪造的数据(相当于商品订单),实现了Future模式的客户并不急于进行处理,而去调用了其它业务逻辑,充分利用了等待时间,这就是Future模式的核心所在。
// 阻塞线程,没有利用到多线程
public static void main(String[] args) throws InterruptedException{
long startTime = System.currentTimeMillis();
// 第一步 网购厨具
OnlineShopping thread = new OnlineShopping();
thread.start();
thread.join();// 保证厨具送到
// 第二步 去超市购买食材
Thread.sleep(2000);// 模拟购买食材时间
Shicai shicai = new Shicai();
System.out.println("第二步:食材到位");
// 使用厨具和食材
System.out.println("第三步:开始展示厨艺");
cook(thread.chuju, shicai);
System.out.println("总共费时"+(System.currentTimeMillis() - startTime));
}
static class OnlineShopping extends Thread {
private Chuju chuju;
@Override
public void run() {
System.out.println("第一步:下单");
System.out.println("第一步:等待送货");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第一步:快递送到");
}
}
static void cook (Chuju chuju, Shicai shicai){}
static class Chuju {}
static class Shicai {}
打印内容
第一步:下单
第一步:等待送货
第一步:快递送到
第二步:食材到位
第三步:开始展示厨艺
总共费时7002
可以看到多线程已经失去了意义。在厨具送到之前我们不能干任何事情。对应代码,就是join方法阻塞主线程。
3. JDK的内置实现
Future模式如此常用,以至于在JDK的并发包中,就已经内置了一种Future模式的实现。其中最为重要的模块是FutureTask类,它实现了Runnable接口,做为单独的线程运行。在run方法中,通过Sync内部类,调用Callable接口,并维护Callable接口的返回对象。当使用FutureTask.get方法时,将返回Callable的返回对象。它还可以取消Future任务,或者设定Future任务的超时时间。
// 耗时5001ms
public static void main(String[] args) throws InterruptedException,
ExecutionException{
long startTime = System.currentTimeMillis();
Callable<Chuju> onlineShopping= new Callable<Chuju>() {
@Override
public Chuju call() throws Exception {
System.out.println("第一步:下单");
System.out.println("第一步:等待送货");
Thread.sleep(5000);
System.out.println("第一步:快递送到");
return new Chuju();
}
};
FutureTask<Chuju> task = new FutureTask<Chuju>(onlineShopping);
new Thread(task).start();
// 第二步 去超市购买食材
Thread.sleep(2000);// 模拟购买食材时间
Shicai shicai = new Shicai();
System.out.println("第二步:食材到位");
// 第三步,使用厨具和食材
if(!task.isDone()) {// 询问快递员,是否到货
System.out.println("厨具还没等,如果不想等就调用cancel方法取消订单");
}
Chuju chuju = task.get();// 获取计算结果(如果还没计算完,也是必须等待的)
System.out.println("第三步:开始展示厨艺");
cook(chuju, shicai);
System.out.println("总共费时"+(System.currentTimeMillis() - startTime));
}
static void cook (Chuju chuju, Shicai shicai){}
static class Chuju {}
static class Shicai {}
Callable接口是一个用户自定义的实现。在应用程序中,通过实现Callable接口的call方法,指定FutureTask的实际工作内容和返回对象。
4.1.2 Master-Worker模式
Master-Worker模式是常用的并行模式之一。它的核心思想是,系统由两类进程协作工作:Master进程和Worker进程。Master负责接受和分配任务,Worker负责处理子任务。当各个Worker进程将子任务处理完成后,将结果返回给Master进程,由Master进程做归纳和汇总,从而得到系统的最终结果。它的优势是,能够将一个大任务分解成若干个小任务,并行执行,从而提供系统的吞吐量。而对于一个client来说,任务一旦提交,Master进程会分配任务并立即返回,并不会等待系统全部处理完成后再返回,其处理过程是异步的。因此Client不会出现等待现象。
public class Master {
// 任务队列
protected Queue<Object> workQueue = new ConcurrentLinkedDeque<>();
// Work进程队列
protected Map<String, Thread> threadMap = new HashMap<>();
// 子任务处理结果集
protected Map<String, Object> resultMap = new ConcurrentHashMap<>();
// 是否所有的子任务都结束了
public boolean isCompelete() {
for(Map.Entry<String , Thread> entry:threadMap.entrySet()){
if(entry.getValue().getState()!=Thread.State.TERMINATED){
return false;
}
}
return true ;
}
// Master的构造,需要一个Worker进程逻辑,和需要的Worker进程数量
public Master(Worker worker, int countWorker) {
worker.setWorkQueue(workQueue);
worker.setResultMap(resultMap);
for(int i=0; i<countWorker; i++) {
threadMap.put(Integer.toString(i), new Thread(worker, Integer.toString(i)));
}
}
// 提交一个任务
public void submit(Object object){
workQueue.add(object);
}
public Map<String, Object> getResultMap() {
return resultMap;
}
// 开始运行所有的Worker进程,进行处理
public void execute() {
threadMap.forEach((k, v) -> {
v.start();
});
}
public static void main(String[] args) {
long begin = System.currentTimeMillis();
// int re = threadCal();
int re = simpleCal();
System.out.println("this result is :" + re);
System.out.println("total time is " + (System.currentTimeMillis() - begin));
}
private static int simpleCal() {
int total = 0;
for(int i=0; i<100; i++) {
total += (i*i*i);
}
return total;
}
private static int threadCal() {
Master m = new Master(new PlusWorker(), 5);// 固定使用5个Worker
for(int i=0; i<100; i++) {
m.submit(i);// 提交100个子任务
}
m.execute();// 开始计算
int re = 0;// 最终结果保存在此
Map<String, Object> resultMap = m.getResultMap();
// 不需要等待所有Worker都执行完,即可开始计算
while(resultMap.size()>0 || !m.isCompelete()) {
Set<String> keys = resultMap.keySet();// 开始计算最终结果
String key = null;
for(String k : keys) {
key = k;
break;
}
Integer i = null;
if(key != null)
i = (Integer)resultMap.get(key);
if(i!= null)
re +=i;
if(key!=null)
resultMap.remove(key);
}
return re;
}
}
public class Worker implements Runnable {
// 任务队列,用于取得子任务
protected Queue<Object> workQueue;
// 子任务处理结果集
protected Map<String, Object> resultMap;
public void setWorkQueue(Queue<Object> workQueue) {
this.workQueue = workQueue;
}
public void setResultMap(Map<String, Object> resultMap) {
this.resultMap = resultMap;
}
// 子任务处理的逻辑,在子类中实现具体逻辑
public Object handle(Object input) {
return input;
}
@Override
public void run() {
while(true) {
Object input = workQueue.poll();
if(null == input) break;
Object re = handle(input);
// 将处理结果写入结果集
resultMap.put(Integer.toString(input.hashCode()), re);
}
}
}
public class PlusWorker extends Worker{
// 求立方和
public Object handle(Object input) {
Integer i = (Integer) input;
return i*i*i;
}
}
4.1.3 Guarded Suspension模式
Guarded Suspension意为保护暂停,其核心思想是仅当服务进程准备好时,才提供服务。设想一种场景,服务器可能会在很短时间内承受大量的客户端请求,客户端请求的数量可能超过服务器本身的即时处理能力,而服务端程序又不能丢弃任何一个客户请求。此时,最佳的处理方案莫过于让客户端请求进行排队,由服务端程序一个接一个处理。这样,既保证了所有的客户端请求均不丢失,同时也避免了服务器由于同时处理太多请求而崩溃。
1 Guarded Suspension模式的结构
public class Request {
private String name;
// 模拟请求内容
public Request(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Request [name=" + name + "]";
}
}
public class RequestQueue {
private LinkedList<Request> queue = new LinkedList<>();
public synchronized Request getRequest() {
while(queue.size() == 0) {
try {
wait();// 等待直到有新的Request加入
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return queue.remove();// 返回Request队列中的第一个请求
}
public synchronized void addRequest(Request request) {
queue.add(request);// 加入新的Request请求
notifyAll();// 通知getRequest()方法,唤醒正在等待对象监视器的所有线程。 线程通过调用wait方法之一等待对象的监视器。
}
}
public class ServerThread extends Thread {
private RequestQueue requestQueue;// 请求队列
public ServerThread(RequestQueue requestQueue, String name) {
super(name);
this.requestQueue = requestQueue;
}
public void run() {
while(true) {
final Request request = requestQueue.getRequest();// 得到请求
try {
Thread.sleep(100);// 模拟请求处理耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "handles" + request);
}
}
}
public class ClientThread extends Thread {
private RequestQueue requestQueue;// 请求队列
public ClientThread(RequestQueue requestQueue, String name) {
super(name);
this.requestQueue = requestQueue;
}
public void run() {
for(int i=0; i<10; i++) {
// 构造请求
Request request = new Request("RequestID: " + i + " Thread_Name:"+Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName()+" requests " + request);
requestQueue.addRequest(request);// 提交请求
try {
Thread.sleep(10);// 客户端请求的速度
} catch (InterruptedException e) {
e.printStackTrace();//
}
System.out.println("ClientThread Name is " + Thread.currentThread().getName());
}
System.out.println(Thread.currentThread().getName() + " request end");
}
public static void main(String[] args) {
RequestQueue requestQueue = new RequestQueue();
for(int i=0; i<10; i++)
new ServerThread(requestQueue, "ServerThread"+i).start();// 服务器进程开启
for(int i=0; i<10; i++)
new ClientThread(requestQueue, "ChientThread"+i).start();// 请求进程开启
}
}
在main函数中,开启了10个Client进程和10个Server处理进程。由于Client进程的请求数高于Server的处理速度,因此RequestQueue发挥了中间缓存的作用。从结果可以看出,所有的ClientThread陆续运行结束,但是RequestQueue中仍有大量的请求,于是ServerThread便陆续工作,知道所有的Request请求均得到处理,客户端的请求没有丢失。
3.携带返回结果的Guarded Suspension
前面提到的Guarded Suspension模式虽然使用了用户请求列表,从而有序地对客户的请求进行处理。但是,客户进程的Request不能获得服务进程的返回结果。当客户进程必须使用服务进程的返回值时,这个结构就无法胜任了。因为,客户进程不知道服务进程何时可以处理这个请求,也不知道需要处理多久。对此,需要对它进行加强。结合前文提到的Future模式,便很容易对Guarded Suspension模式进行扩展,构造一个可以携带返回值的Guarded Suspension。
public interface Data {
public String getResult();
}
public class RealData implements Data{
protected final String result;
public RealData(String para) {
StringBuffer sb = new StringBuffer();
for(int i=0; i<10; i++) {
sb.append(para);
try {
// 这里使用sleep来代替逻辑处理
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
result = sb.toString();
}
public String getResult() {
return result;
}
}
public class FutureData implements Data{
protected RealData realData = null;// FutureData是RealDta的包装
protected boolean isReady = false;
public synchronized void setRealData(RealData realData) {
if (isReady) {
return;
}
this.realData = realData;
isReady = true;
notifyAll();// realData已经被注入,通知getResult()
}
// 会等待RealData构造完成
public synchronized String getResult() {
while(!isReady) {
try {
wait();// 一直等待,直到RealData被注入
} catch (InterruptedException e) {
}
}
return realData.result;// 由RealData实现
}
}
public class Request {
private String name;
private Data response;// 请求的返回值
// 模拟请求内容
public Request(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public synchronized Data getResponse() {
return response;
}
public synchronized void setResponse(Data response) {
this.response = response;
}
@Override
public String toString() {
return "Request [name=" + name + "]";
}
}
public class RequestQueue {
private LinkedList<Request> queue = new LinkedList<>();
public synchronized Request getRequest() {
while(queue.size() == 0) {
try {
wait();// 等待直到有新的Request加入
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return queue.remove();// 返回Request队列中的第一个请求
}
public synchronized void addRequest(Request request) {
queue.add(request);// 加入新的Request请求
notifyAll();// 通知getRequest()方法
}
}
public class ServerThread extends Thread {
private RequestQueue requestQueue;// 请求队列
public ServerThread(RequestQueue requestQueue, String name) {
super(name);
this.requestQueue = requestQueue;
}
public void run() {
while(true) {
final Request request = requestQueue.getRequest();// 得到请求
final FutureData future = (FutureData) request.getResponse();
// RealData的创建比较耗时
RealData realData = new RealData(request.getName());
future.setRealData(realData);
System.out.println(Thread.currentThread().getName() + " handles " + request);
}
}
}
public class ClientThread extends Thread {
private RequestQueue requestQueue;// 请求队列
private List<Request> myRequest = new ArrayList<Request>();
public ClientThread(RequestQueue requestQueue, String name) {
super(name);
this.requestQueue = requestQueue;
}
public void run() {
for(int i=0; i<10; i++) {
// 构造请求
Request request = new Request("RequestID: " + i + " Thread_Name:"+Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName()+" request is " + request);
request.setResponse(new FutureData());
requestQueue.addRequest(request);// 提交请求
myRequest.add(request);
// 这里可以做一些额外的业务处理,等待服务端装配数据
try {
Thread.sleep(1000);// 客户端请求的速度
} catch (InterruptedException e) {
e.printStackTrace();//
}
for(Request r : myRequest) {
System.out.println("ClientThread Name is " + Thread.currentThread().getName() +" Response is" +
r.getResponse().getResult());
}
}
System.out.println(Thread.currentThread().getName() + " request end");
}
public static void main(String[] args) {
RequestQueue requestQueue = new RequestQueue();
for(int i=0; i<1; i++)
new ServerThread(requestQueue, "ServerThread"+i).start();// 服务器进程开启
// try {
// Thread.sleep(10000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println("客户端发起请求...");
List<Request> requests = new ArrayList<>();
for(int i=0; i<1; i++){
new ClientThread(requestQueue, "ChientThread"+i).start();;// 请求进程开启
}
}