正确的使用Fork/Join框架,需要一定熟悉它的结构,对于一个分布式的任务,必然具备两种条件:①任务调度;②任务执行。在Fork/Join中,我们主要用它自定义的线程池来提交任务和调度任务,称之为:ForkJoinPool;同时我们有它自己的任务执行类,称之为:ForkJoinTask。
不过我们不直接使用ForkJoinTask来直接执行和分解任务,我们一般都使用它的两个子类,RecursiveAction和RecursiveTask,其中,前者主要处理没有返回结果的任务,后者主要处理有返回结果的任务。总结一下,以下就是Fork/Join的基本模型:
用代码实现Fork/Join实现大数据计算:
需求:计算1+2+3+……..+N的和
以下是用Fork/Join进行计算,主要的核心思想就是把超大的计算拆分为小的计算,通俗来说就是把一个极大的任务拆分为很多个小任务,下面是核心计算模型:
代码实现如下:
package com.brickworkers;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class FockJoinTest extends RecursiveTask
//设立一个最大计算容量
private final intDEFAULT_CAPACITY = 10000;
//用2个数字表示目前要计算的范围
private int start;
private int end;
public FockJoinTest(intstart, int end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute(){//实现compute方法
//分为两种情况进行出来
long sum = 0;
//如果任务量在最大容量之内
if(end - start
for (int i =start; i < end; i++) {
sum += i;
}
}else{//如果超过了最大容量,那么就进行拆分处理
//计算容量中间值
int middle =(start + end)/2;
//进行递归
FockJoinTestfockJoinTest1 = new FockJoinTest(start, middle);
FockJoinTestfockJoinTest2 = new FockJoinTest(middle + 1, end);
//执行任务
fockJoinTest1.fork();
fockJoinTest2.fork();
//等待任务执行并返回结果
sum =fockJoinTest1.join() + fockJoinTest2.join();
}
return sum;
}
public static void main(String[] args) {
ForkJoinPoolforkJoinPool = new ForkJoinPool();
FockJoinTestfockJoinTest = new FockJoinTest(1, 100000000);
longfockhoinStartTime = System.currentTimeMillis();
//前面我们说过,任务提交中invoke可以直接返回结果
long result = forkJoinPool.invoke(fockJoinTest);
System.out.println("fock/join计算结果耗时"+(System.currentTimeMillis() - fockhoinStartTime));
long sum = 0;
long normalStartTime= System.currentTimeMillis();
for (int i = 0; i< 100000000; i++) {
sum += i;
}
System.out.println("普通计算结果耗时"+(System.currentTimeMillis() - normalStartTime));
}
}
//执行结果:
//fock/join计算结果耗时33
//普通计算结果耗时141
Java并发包(java.util.concurrent)总结
1)并发容器
ConcurrentHashMap
有序的ConcurrentSkipListMap
线程安全的动态数组CopyOnWriteArrayList
2)同步设备
CountDownLatch
CyclicBarrier
Semaphore,可作为资源控制器,限制同时进行工作的线程数量
3)各种并发队列
BlockedQueue、ArrayBlockingQueue、SynchorousQueue或特定场景的
PriorityBlockingQueue等
4)原子对象
AtomicInteger
AtomicBoolean
…
5)锁
ReentrantLock
Condition
ReentrantReadWriteLock
ReentrantReadWriteLock.WriteLock
ReentrantReadWriteLock.ReadLock
6)执行器和线程池
ExecutorService
CompletionService
ScheduledExecutor
Callable
FutureTask
7)Fork-join 框架
多线程开发良好的实践
* 给线程起个有意义的名字,这样可以方便找 Bug。
* 缩小同步范围,从而减少锁争用。例如对于 synchronized,应该尽量使用同步块而不是同步方法。
* 多用同步工具少用 wait() 和 notify()。首先,CountDownLatch, CyclicBarrier, Semaphore 和 Exchanger 这些同步类简化了编码操作,而用 wait() 和 notify() 很难实现复杂控制流;其次,这些同步类是由最好的企业编写和维护,在后续的 JDK 中还会不断优化和完善。
* 使用 BlockingQueue 实现生产者消费者问题。
* 多用并发集合少用同步集合,例如应该使用 ConcurrentHashMap 而不是 Hashtable。
* 使用本地变量和不可变类来保证线程安全。
* 使用线程池而不是直接创建线程,这是因为创建线程代价很高,线程池可以有效地利用有限的线程来启动任务。
参考书目:《Java并发编程实战》、《Java编程思想》、《Java核心技术卷一》、《Effective Java》、《Java并发编程的艺术》