并发算法虽然可以充分发挥多核CPU的性能,但并非所有的计算都可以改造成并发形式。执行过程中有数据相关性的运算都是无法完美并行化的。


假如现在有两个数,B和C。如果要计算(B+C)*B/2,那么这个运算过程就是无法并行的。原因是,如果B+C没有执行完成,则永远算不出(B+C)*B,这就是数据相关性。


遇到这种情况,可以借鉴日常生产中的流水线思想。


类似的思想可以借鉴到程序开发中。即使(B+C)*B/2无法并行,但是如果需要计算一大堆B和C,可以将它流水话。首先将计算过程拆分为三个步骤:


P1:A=B+C


P2:D=AxB


P3:D=D/2


上述步骤中P1、P2和P3均在单独的线程中计算,并且每个线程只负责自己的工作。此时,P3的计算结果就是最终需要的答案。


P1接收B和C的值,并求和,将结果输入P2。P2求乘积后输入给P3。P3将D除以2得到最终值。一旦这条流水线建立,只需要一个计算步骤就可以得到(B+C)*B/2的结果。


为了实现这个功能,需要定义一个在线程间携带结果进行信息交换的载体:


public class Msg {
	public double i;
	public double j;
	public String orgStr = null;
}


P1计算的是加法:


public class Plus implements Runnable {
	public static BlockingQueue<Msg> bq = new LinkedBlockingQueue<Msg>();
	@Override
	public void run() {
		while(true) {
			try {
				Msg msg = bq.take();
				msg.j = msg.i + msg.j;
				Multiply.bq.add(msg);
			} catch (InterruptedException e) {
			}
		}
	}
}


上述代码中,P1取得封装了两个操作数的Msg,并进行求和,将结果传递给乘法线程P2。当没有数据需要处理时,P1进行等待。


P2计算乘法:


public class Multiply implements Runnable {
	public static BlockingQueue<Msg> bq = new LinkedBlockingQueue<Msg>();
	@Override
	public void run() {
		while(true) {
			try {
				Msg msg = bq.take();
				msg.i = msg.i * msg.j;
				Div.bq.add(msg);
			} catch (InterruptedException e) {
			}
		}
	}
}


和P1非常类似,P2计算相乘结果后,将中间结果传递给除法线程P3。


P3计算除法:


public class Div implements Runnable {
	public static BlockingQueue<Msg> bq = new LinkedBlockingQueue<Msg>();
	@Override
	public void run() {
		while(true) {
			try {
				Msg msg = bq.take();
				msg.i = msg.i / 2;
				System.out.println(msg.orgStr + "=" + msg.i);
			} catch (InterruptedException e) {
			}
		}
	}
}


P3将结果除以2后输出最终结果。


最后是提交任务的主线程,这里,提交100万个请求,让线程组进行计算:


public class PStreamMain {
	public static void main(String[] args) {
		new Thread(new Plus()).start();
		new Thread(new Multiply()).start();
		new Thread(new Div()).start();
		
		long s1 = System.currentTimeMillis();
		for(int i=1; i<=1000; i++) {
			for(int j=1; j<=1000; j++) {
				Msg msg = new Msg();
				msg.i = i;
				msg.j = j;
				msg.orgStr = "((" + i + "+" + j + ")*" + i + ")/2";
				Plus.bq.add(msg);
			}
		}
	}
}


上述代码中,将数据提交给P1加法线程,开启流水线的计算。在多核或者分布式场景中,这种设计思路可以有效地将有依赖关系的操作分配在不同的线程中进行计算,尽可能利用多核优势。


注:本篇博客内容摘自《Java高并发程序设计》