public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize:核心线程数,就是不管有没有任务要执行,也有corePoolSize个线程在候着等任务。
maximumPoolSize:最大线程数,不管提交多少任务,线程池中最多的工作线程数就是maximumPoolSize。
keepAliveTime:线程存活时间,当线程池里的线程数大于corePoolSize时,如果等了keepAliveTime时长还没有任务执行,则线程退出。
unit:线程存活时间的单位。
workQueue:一个阻塞队列,提交的任务将会被放到这个队列中。
threadFactory:线程工厂,用来创建线程,主要是为了给线程起名字,默认工厂的线程名字pool-1-thread-3。
handler:拒绝策略,当线程池里的线程被耗尽,队列也满了的时候调用。
比如有100个任务,核心线程数10,最大线程数20,队列最大长度10,当第31个任务来了之后就会报错。因为没有线程和队列去执行它就会使用拒绝策略,抛出异常。
ThreadPoolExecutor线程池的工作原理,共用一个队列,线程池里的总量也是有限的。
newcachedThreadPool()
newFixedThreadPool()
newSingleThreadExecutor()
底层都是创建newThreadPoolExecutor(),参数不一样而已。
ForkJoinPool没有继承ThreadPoolExecutor,工作机制与其不同,为了解决cpu密集型的工作。它的继承体系如下:
Fork/Join 框架是 Java7 提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。
Fork 就是把一个大任务切分为若干子任务并行的执行,Join 就是合并这些子任务的执行结果,最后得到这个大任务的结果。比如计算1+2+…+10000,可以分割成 10 个子任务,每个子任务分别对 1000 个数进行求和,最终汇总这 10 个子任务的结果。如下图所示:
Fork/Jion特性:
核心思想:把大任务拆分成小任务,任务粒度足够小,充分利用cpu的多核多线程处理能力,一个cpu执行完任务就可以帮其他cpu的忙,提高cpu的利用率,减少cpu的摸鱼时间。
工作窃取(work-stealing)算法是指某个线程从其他队列里窃取任务来执行。
我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不依赖的子任务,为了减少线程间的竞争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应,比如A线程负责处理A队列里的任务。但是有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。
工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并且消耗了更多的系统资源,比如创建多个线程和多个双端队列。
private ForkJoinPool(int parallelism,
ForkJoinWorkerThreadFactory factory,
UncaughtExceptionHandler handler,
int mode,
String workerNamePrefix) {
this.workerNamePrefix = workerNamePrefix;
this.factory = factory;
this.ueh = handler;
this.config = (parallelism & SMASK) | mode;
long np = (long)(-parallelism); // offset ctl counts
this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK);
}
重要参数解释
①parallelism:并行度( the parallelism level),默认情况下跟我们机器的cpu个数保持一致,使用 Runtime.getRuntime().availableProcessors()可以得到我们机器运行时可用的CPU个数。
②factory:创建新线程的工厂( the factory for creating new threads)。默认情况下使用ForkJoinWorkerThreadFactory defaultForkJoinWorkerThreadFactory。
③handler:线程异常情况下的处理器(Thread.UncaughtExceptionHandler handler),该处理器在线程执行任务时由于某些无法预料到的错误而导致任务线程中断时进行一些处理,默认情况为null。
④asyncMode:这个参数要注意,在ForkJoinPool中,每一个工作线程都有一个独立的任务队列,asyncMode表示工作线程内的任务队列是采用何种方式进行调度,可以是先进先出FIFO,也可以是后进先出LIFO。如果为true,则线程池中的工作线程则使用先进先出方式进行任务调度,默认情况下是false。
fork() 做的工作只有一件事,既是把任务推入当前工作线程的工作队列里
ForkJoinPool 自身拥有工作队列,这些工作队列的作用是用来接收由外部线程(非 ForkJoinThread 线程)提交过来的任务,而这些工作队列被称为 submitting queue 。
submit() 和 fork() 其实没有本质区别,只是提交对象变成了 submitting queue 而已(还有一些同步,初始化的操作)。submitting queue 和其他 work queue 一样,是工作线程”窃取“的对象,因此当其中的任务被一个工作线程成功窃取时,就意味着提交的任务真正开始进入执行阶段。
ForkJoinTask 在执行的时候可能会抛出异常,但是我们没办法在主线程里直接捕获异常,所以 ForkJoinTask 提供了 isCompletedAbnormally() 方法来检查任务是否已经抛出异常或已经被取消了,并且可以通过 ForkJoinTask 的 getException 方法获取异常。示例如下:
if(task.isCompletedAbnormally()){
System.out.println(task.getException());
}
ForkJoinTask:我们要使用 ForkJoin 框架,必须首先创建一个 ForkJoin 任务。它提供在任务中执行 fork() 和 join() 操作的机制,通常情况下我们不需要直接继承 ForkJoinTask 类,而只需要继承它的子类,Fork/Join 框架提供了以下两个子类:
RecursiveAction:用于没有返回结果的任务。(比如写数据到磁盘,然后就退出了。 一个RecursiveAction可以把自己的工作分割成更小的几块, 这样它们可以由独立的线程或者CPU执行。 我们可以通过继承来实现一个RecursiveAction)
RecursiveTask :用于有返回结果的任务。(可以将自己的工作分割为若干更小任务,并将这些子任务的执行合并到一个集体结果。 可以有几个水平的分割和合并)
CountedCompleter: 在任务完成执行后会触发执行一个自定义的钩子函数onCompletion(CountedCompleter> caller)
1、数据清洗;2、排序;3、查找
ForkJoinPool适合于大数据量的场景,ForkJoinPool首先要拆分任务,之后才会有多个线程去执行任务,最后归并结果,而且这个过程中ForkJoinPool还有上下文切换,如果数据量比较小的话,这个时间内单线程可能已经运行结束了,所以说ForkJoinPool一定要用在合适的场景。
public class TestRecursiveAction {
public static void main(String[] args) throws InterruptedException {
ForkJoinPool forkJoinPool = new ForkJoinPool();
PrintTask task = new PrintTask(0,300); //打印 0-299,并不是连续的。
forkJoinPool.submit(task);
//System.out.println( "线程数:" + forkJoinPool.getActiveThreadCount()); //1
System.out.println("并行数量:" + forkJoinPool.getParallelism());//8 并行数量,默认cpu的逻辑处理数
System.out.println("可以使用的处理器" + Runtime.getRuntime().availableProcessors());//4
forkJoinPool.awaitTermination(2, TimeUnit.SECONDS);
forkJoinPool.shutdown();
}
}
class PrintTask extends RecursiveAction {
private static final int LENGTH = 50;
private int start;
private int end;
public PrintTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected void compute() {
if ((end - start) < LENGTH) {
for (int i = start; i < end; i++) {
System.out.println(Thread.currentThread().getName() + " ---" + i);
}
} else {
int middle = (start + end) / 2;
PrintTask low = new PrintTask(start, middle);
PrintTask high = new PrintTask(middle, end);
//分两个任务。
low.fork();
high.fork();
}
}
}
public class TestRecursiveTask {
public static void main(String[] args) throws ExecutionException, InterruptedException {
int[] arr = new int[10000];
Random random = new Random();
int total = 0;
long start = System.currentTimeMillis();
for(int i = 0; i < arr.length; i++) {
int temp = random.nextInt(20);
total += (arr[i] = temp);
}
System.out.println(total);
//System.out.println(System.currentTimeMillis()-start);
start = System.currentTimeMillis();
ForkJoinPool pool = new ForkJoinPool();
Future future = pool.submit(new Sum(arr,0,arr.length));
System.out.println(future.get());
//System.out.println(System.currentTimeMillis()-start);
}
}
class Sum extends RecursiveTask<Integer> {
private int arr[];
private int start;
private int end;
private static final int THRESHOLD = 10;
public Sum(int[] arr, int start, int end) {
this.arr = arr;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if(end - start < THRESHOLD) {
int sum = 0;
for(int i = start; i < end; i++) {
sum += arr[i];
}
return sum;
}else {
int middle = (start + end) / 2;
Sum lowSum = new Sum(arr,start,middle);
Sum highSum = new Sum(arr,middle,end);
//分成两个任务
lowSum.fork();
highSum.fork();
//把两个任务的结果累加合并在一起
return lowSum.join() + highSum.join();
}
}
}
补充:
在使用ForkJoin时需要注意: 如果任务的划分层次很深,一直得不到返回,那么可能出现两种情况:第一,系统内的线程数量越积越多,导致性能严重下降;第二,函数的调用层次变的很深,最终导致栈溢出。
使用ForkJoinPool的时候发现执行任务的方法有三种: