【Java程序性能优化 第一版】第四章(Future, Master-Worker,Guarded Suspension模式)

                                 第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();;// 请求进程开启
        }

    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值