相对于串行编程来说,并行编程有如下三个主要目标:
- 性能 - 因为现在CPU性能已经遇到瓶颈,使用单线程编程又无法发挥多核CPU的性能
- 生产率 - 提高创建并行软件的生产率
- 通用性 - 开发并行程序需要很高的成本,更加通用的并行程序能够有效降低成本。然而通用性又会带来更大的性能损失和生产率损失。参考一下现在流行的我所知道的并行编程环境:
java的编程环境与生俱来提供多线程能力,要实现并行编程还需解决以下一些问题:
1、给定一个单线程的顺序进程,单线程对所有进程的资源都有访问权。
2、线程如何协调访问资源,这种协调是由非常多的同步机制通过提供不同的并行语言和环境来实施的,包括消息传递、加锁、事务、引用计数、显示计时、共享原子变量以及数据所有权。在传统编程基础上引出死锁、活锁以及事务回滚。
实现方法一:通过jdk5新增的CountDownLatch(相当于计数器)对象实现,基本思路就是每执行完一个线程调用CountDownLatch.countDown()方法将计数器减1,主线程执行CountDownLatch.await()等待计数器减为0唤醒。
具体代码可参考http://blog.csdn.net/wangmuming/article/details/19832865。
实现方法二:
通过ExecutorService executor = Executors.newFixedThreadPool(int threadNum) 创建线程池
调用List<Future<T>> result = executor.invokeAll( Collection<?extends Callable<T> tasks>)赋予线程池n个任务,并自动执行,在执行过程中主线程会阻塞直到所有任务执行完毕。执行完毕后将结果存储在Future对象中,通过Future.get()对象获取返回结果。
package com.nuaa.ldm;
import java.util.*;
import java.util.concurrent.*;
import static java.util.Arrays.asList;
public class TaskTest {
static class Sum implements Callable<Long> {
private final longfrom;
private final long to;
Sum(long from,long to) {
this.from = from;
this.to = to;
}
@Override
public Long call() {
long acc = 0;
for (long i =from; i <= to; i++) {
acc = acc + i;
}
return acc;
}
}
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(2);
List<Future<Long>> results = executor.invokeAll(asList(
new Sum(0, 10),new Sum(100, 1000), new Sum(10000, 1000000)
));
executor.shutdown();
for(Future<Long> result : results) {
System.out.println(result.get());
}
}
}
实现方法三:
Fork-joint框架,通过work-steaking算法实现,这种算法是一种调度算法,每个工作线程用一个双端队列维护一组任务。Fork的时候是把任务加到队列的头部,而不像一般的线程池那样是加到任务队列末尾。工作线程选择头部最新的任务来执行。当工作线程没有任务可执行时,它会尝试从其它线程的任务队列尾部窃取一个任务执行。如果没有任务执行了并且窃取其它任务失败,那么工作线程停止。
这种方法的优点是减少了争用,因为工作线程从头获取任务,而窃取线程从尾部窃取任务。这种调度算法较适用于递归算法,递归算法采用分治思想,使得早期产生的任务单元较大,因而窃取的较大任务进一步递归分解,因此减少尾部窃取次数。另外,父任务很可能要等待子任务(join),所以从队列头部子任务开始执行也是一种优化。
总之,它会使用有限的线程执行大量任务,同时保持各线程的任务都处于繁忙的执行状态,而尽量不让线程处于等待状态。为了做到这点可能会从其它线程的任务队列中窃取任务来执行,所以叫work-stealing。
简单例子:计算斐波那契数
package com.nuaa.ldm;
import java.math.BigInteger;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
public class Fibonacci extends RecursiveTask<BigInteger>{
int n ;
public Fibonacci() {
}
Fibonacci(int i) {
this.n = i;
}
private BigInteger compute(int small) {
final int[] results = { 1, 1, 2, 3, 5,8, 13, 21, 34, 55, 89 };
return new BigInteger(results[small]+"");
}
@Override
protected BigIntegercompute() {
if(n < 10){ //任务足够小,直接查表返回
return compute(n);
}
System.out.println(Thread.currentThread().getName()+"");
Fibonacci f1= new Fibonacci(n-1);//创建子任务
Fibonacci f2= new Fibonacci(n-2);//创建子任务
f2.fork();//子任务执行
return f1.compute().add(f2.join());//两个子任务结果的合并
}
public static voidmain(String[] args) {
/*线程的数量有限,超过一定的数量会报错,内存溢出*/
ForkJoinPool forkJoinPool = new ForkJoinPool();
Fibonacci fibonacci = new Fibonacci(50);//创建任务
Future<BigInteger> result = forkJoinPool.submit(fibonacci);
try {
System.out.println(result.get());
} catch(InterruptedException e) {
e.printStackTrace();
} catch(ExecutionException e) {
e.printStackTrace();
}
}
}
复杂例子:
package com.nuaa.ldm;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
/** 计算1^2+2^2+3^2+4^2+5^2+6^2通过二分来分段计算,而二分法的会将数组分成若干段,然后分别进行计算
* */
public class FibonacciAction extends RecursiveAction{
final double[] array;
final int lo, hi;
double result;
FibonacciAction next; // keeps track ofright-hand-side tasks
FibonacciAction(double[] array, int lo, inthi, FibonacciAction next) {
this.array = array; this.lo = lo; this.hi= hi;
this.next = next;
}
/*计算数组下表从[l,h)平方和*/
double atLeaf(int l, int h) {
double sum = 0;
for (int i = l; i < h; ++i) // performleftmost base step
sum += array[i] * array[i];
return sum;
}
/*分治(递归过程)*/
protected void compute() {
int l = lo;
int h = hi;
FibonacciAction right = null;
/*二分,并且要求SurplusQueuedTaskCount小于3*/
while (h - l > 1 &&getSurplusQueuedTaskCount() <= 3) {
int mid = (l + h) >>> 1;
/*创建一个子任务*/
right = new FibonacciAction(array, mid,h, right);
right.fork();
h = mid;
}
double sum = atLeaf(l, h);
while (right != null) {
/*用剩余时间去执行队列中的其他线程任务,提高效率,使得线程处于忙碌状态*/
if (right.tryUnfork()) // directlycalculate if not stolen
sum += right.atLeaf(right.lo,right.hi);
else {
/*等待执行结果*/
right.join();
sum += right.result;
}
right = right.next;
}
result = sum;
}
public static voidmain(String[] args) {
System.out.println(sumOfSquares(newForkJoinPool(),new double[]{1,2,3,4,5,6}));
}
static double sumOfSquares(ForkJoinPool pool,double[] array) {
int n = array.length;
FibonacciAction a = newFibonacciAction(array, 0, n, null);
pool.invoke(a);
return a.result;
}
}
其中getSurplusQueuedTaskCount()方法统计当前工作线程所持有任务的估计数量,扣去工作线程扣去可能会被窃取的fork的子任务数量。
tryUnfork()方法尝试取消一个新fork的任务,如果取消成功返回true,否则返回false。