文章目录
1 Fork/Join
1.1 Fork/Join概念
1.1.1 Fork/Join定义
fork/join框架是把一个大任务分割成若干个小任务,最终汇总每个任务结果后得到大任务结果的框架
- 分割任务: 先将大任务分割成子任务,如果子任务还是很大,则继续分割,直到分割出的子任务足够小
- 执行任务并合并结果: 分割的子任务分别放在双端队列里,然后几个启动线程分别从双端队列里获取任务执行. 子任务执行完的结果都统一放在一个队列里,启动一个线程从队列里拿数据,然后合并这些数据
1.1.2 工作窃取(work-stealing)算法
是指某个线程从其他队列里窃取任务来执行
-
假如需要做一个比较大的任务,可以将这个任务分割为若干互不依赖的子任务,为了减少线程间的竞争,把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应,A线程负责处理A队列里的任务.
-
有的线程会把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理. 如果这时干完活的线程等待着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行.
-
这时这个它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行
-
工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是在某些情况下还是存在竞争,比如双端队列里只有一个任务时.并且消耗了更多的系统资源,比如创建了多个线程和多个双端队列
-
ForkJoinPool线程池,与ExecutorService区别在于ForkJoinPool使用了"工作窃取"(线程池中所有的线程都企图找到并执行提交给线程池的任务)
-
ForkJoinTask代表运行在ForkJoinPool中的任务
- RecursiveAction 递归无返回值的ForkJoinTask
- RecursiveTask 递归有返回值的ForkJoinTask
-
ForkJoinWorkerThread代表ForkJoinPool线程池中的一个执行任务的线程
1.2 常用方法
// ForkJoinPool.class
// 以Runtime.getRuntime().availableProcessors()的返回值作为parallelism来创建ForkJoinPool
public ForkJoinPool();
// 创建一个包含parallelism个并行线程的ForkJoinPool
public ForkJoinPool(int parallelism)
// ForkJoinTask.class
// 在当前线程运行的线程池中安排一个异步任务,即创建一个子任务
public final ForkJoinTask<V> fork();
// 当任务完成的时候返回计算结果
public final V join();
// 开始执行任务, 如果有必要,等待计算完成
public final V invoke();
public static void invokeAll(ForkJoinTask<?>... tasks);
1.4 Fork/Join的使用
1.4.1 RecursiveAction 无返回值分治框架
public void testRecursiveAction() throws InterruptedException {
// 创建包含Runtime.getRuntime().availableProcessors()返回值作为个数的并行线程的ForkJoinPool
ForkJoinPool forkJoinPool = new ForkJoinPool();
// 提交可分解的PrintTask任务
forkJoinPool.submit(new MyRecursiveAction(0, 160));
//阻塞当前线程直到ForkJoinPool中所有的任务都执行结束
forkJoinPool.awaitTermination(1, TimeUnit.SECONDS);
// 关闭线程池
forkJoinPool.shutdown();
}
/**
* 无返回值分治框架
* 每个任务20个, 总共160个
*/
static class MyRecursiveAction extends RecursiveAction {
// 每个队列小任务最大数量
public static final int THRESHOLD = 20;
private int start;
private int end;
public MyRecursiveAction(int start, int end) {
this.start = start;
this.end = end;
}
protected void compute() {
if ((end - start) < THRESHOLD) {
for (int i = start; i < end; i++) {
System.out.println("线程名:" + Thread.currentThread().getName() + ",执行任务编号: " + i);
}
} else {
int middle = (start + end) / 2;
new MyRecursiveAction(start, middle).fork();
new MyRecursiveAction(middle, end).fork();
}
}
}
1.4.2 RecursiveTask 有返回值分治框架
public void testRecursiveTask() throws InterruptedException, ExecutionException {
// 创建包含Runtime.getRuntime().availableProcessors()返回值作为个数的并行线程的ForkJoinPool
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> submit = forkJoinPool.submit(new MyRecursiveTask(1, 1000_0000));
System.out.println("计算出总和=" + submit.get());
Long result = forkJoinPool.invoke(new MyRecursiveTask(1, 1000_0000));
System.out.println("计算出总和=" + result);
//关闭线程池
forkJoinPool.shutdown();
}
static class MyRecursiveTask extends RecursiveTask<Long> {
// 每个队列小任务最大数量
public static final int THRESHOLD = 50;
private long start;
private long end;
public MyRecursiveTask(long start, long end) {
this.start = start;
this.end = end;
}
protected Long compute() {
if ((end - start) < THRESHOLD) {
long sum = 0;
for (long i = start; i < end; i++) {
sum += i;
}
return sum;
} else {
long middle = (start + end) / 2;
// 大任务分解成两个小任务
MyRecursiveTask leftTask = new MyRecursiveTask(start, middle);
MyRecursiveTask rightTask = new MyRecursiveTask(middle, end);
// 并行执行两个小任务
leftTask.fork();
rightTask.fork();
// 将两个小任务累加的结果合并起来
return leftTask.join() + rightTask.join();
}
}
}
1.4.3 Lambda实现累加
public void testJdk8Method(){
// 并行流
Instant start = Instant.now();
long result1 = LongStream.rangeClosed(1, 1000_0000).parallel().reduce(0, Long::sum);
Instant end = Instant.now();
System.out.println("并行流 耗费时间: " + Duration.between(start,end).toMillis());
// 顺序流
start = Instant.now();
long result2 = LongStream.rangeClosed(1,1000_0000).sequential().reduce(0,Long::sum);
end = Instant.now();
System.out.println("顺序流 耗费时间: " + Duration.between(start,end).toMillis());
}
2 CountDownLatch
CountDownLatch cd = new CountDownLatch(10);
cd.countDown();//每个线程执行
cd.await();主线程执行
3 CyclicBarrier
- 可以实现线程间的计数等待,且可以重复使用
- CountDownLatch与CyclicBarrier区别
- CountDownLatch放行由第三者控制,CyclicBarrier放行由线程本身控制
- CountDownLatch放行条件是>=线程数,CyclicBarrier放行条件=线程数
4 Semaphore
信号量Semaphore是对锁的扩展,Synchronized和ReentrantLock一次都只允许一个线程访问一个资源,但是信号量可以指定多个线程同时访问某个资源.
4.1 Semaphore主要方法
// 尝试获取一个准入许可,无法获得则等待,直到线程释放一个许可或当前线程被中断.
acquire()
// acquire()方法类似,但不响应中断
acquireUninterruptibly()
// 方法尝试获得一个许可,成功返回true,失败返回false,不进行等待,会立即返回
tryAcquire()
// 方法用于在线程访问资源结束后释放一个许可,以使其他等待许可的线程可以进行资源访问
release()
4.2 Semaphore的使用
public Semaphore(int permits)
public Semaphore(int permits,boolean fair)