持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第20天,点击查看活动详情
3. ForkJoinPool
分叉合并池
分叉:将一个大的任务拆分成了多个小的任务交给不同的线程来执行
合并:将拆分出去的小任务计算结果进行汇总整合
特点:分叉合并池能够有效的提高CPU的利用率,甚至能到到CPU利用率的100%,所以实际开发中,分叉合并一般是放在凌晨执行
本质:分叉合并本质上就是将大任务拆分成小任务,分配给多个线程,让多个线程落在不同的CPU核上来,提高CPU的利用率
效率:
- 如果数据量偏大,分叉合并的效率要远远高于循环
- 如果数据量偏小,循环的效率反而高于分叉合并
(work-stealing)工作窃取策略:
分叉合并在向每个核上分配任务的时候,考虑任务的均衡问题:核上任务多的少分,任务少的多分。由于CPU核在执行任务的时候,有快有慢(有的任务简单执行的就快,有的任务复杂执行的就慢),所以先执行完所有任务的CPU核并不会闲下来,而是会随机的扫描一个其他的核,然后从被扫描的核的任务队列尾端"偷取"一个任务回来执行,这种策略称之为work-stealing(工作窃取)策略
使用
- 功能类实现
RecursiveTask,重写compute()方法 - 创建分叉合并池
submit()提交任务,并接收返回值对象
public class ForkJoinPoolDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
long begin = System.currentTimeMillis();
// 求1-100000000000L
// long sum = 0;
// for (long i = 1; i <= 100000000000L; i++) {
// sum += i;
// }
// System.out.println(sum);
ForkJoinPool pool = new ForkJoinPool();
Future<Long> f = pool.submit(new Sum(1, 100000000000L));
pool.shutdown();
System.out.println(f.get());
long end = System.currentTimeMillis();
System.out.println(end - begin);
}
}
class Sum extends RecursiveTask<Long> {
private long start;
private long end;
public Sum(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
// 如果start-end范围内数字比较少,那么可以将这个范围内的数字求和
if (end - start <= 10000) {
long sum = 0;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
} else {
// 如果start-end数字依然比较多,那么就继续拆分
long mid = (start + end) / 2;
Sum left = new Sum(start, mid);
Sum right = new Sum(mid + 1, end);
// 拆分成了2个子任务
left.fork();
right.fork();
// 需要将2个子任务的结果来进行汇总才是这个范围内的最终结果
return left.join() + right.join();
}
}
}
4. 使用线程池过程的一些问题
4.1 父子线程之间数据的共享问题
4.1.1 每次都new Thread去执行线程
父子线程之间的数据共享通过InheritableThreadLocal类实现,在子线程初始化(new)的时候调用init方法设置的,在子线程中调用InheritableThreadLocal即可和父线程共享数据
/**
* 用于父子线程间共享 LoginUserOrg 对象
*
* @date 2021/07/01
*/
public class LoginUserOrgHolder {
private LoginUserOrgHolder() {
}
private static final ThreadLocal<LoginUserOrg> THREAD_LOCAL = new InheritableThreadLocal<>();
public static LoginUserOrg get() {
return THREAD_LOCAL.get();
}
public static void set(LoginUserOrg obj) {
THREAD_LOCAL.set(obj);
}
public static void remove() {
THREAD_LOCAL.remove();
}
}
在子线程中使用
LoginUserOrgHolder类获取共享线程数据
4.1.2 使用线程池
使用线程池时,会提前在线程池中初始化一些线程,就没办法使用InheritableThreadLocal 类,在子线程init时设置了
所以使用alibaba提供的一个jar包,来解决使用线程池时,数据共享的问题
关键类:TransmittableThreadLocal
import com.alibaba.ttl.TransmittableThreadLocal;
/**
* 用于父子线程间共享 LoginUserOrg 对象
*
* @date 2021/07/01
*/
public final class LoginUserOrgHolder {
private LoginUserOrgHolder() {
}
private static final ThreadLocal<LoginUserOrg> THREAD_LOCAL = new TransmittableThreadLocal<>();
public static LoginUserOrg get() {
return THREAD_LOCAL.get();
}
public static void set(LoginUserOrg obj) {
THREAD_LOCAL.set(obj);
}
public static void remove() {
THREAD_LOCAL.remove();
}
}