一、ThreadPoolExecutor
构造器中各个参数的含义:
- corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
- maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
- keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0
在ThreadPoolExecutor类中有几个非常重要的方法:
- execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
- submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果(Future相关内容将在下一篇讲述)。
- shutdown()和shutdownNow()是用来关闭线程池的。
- 比如:getQueue() 、getPoolSize() 、getActiveCount()、getCompletedTaskCount()等获取与线程池相关属性的方法
ThreadPoolExecutor简单示例
package constant;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 200, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
long s1 = System.currentTimeMillis();
// 计数控制
final CountDownLatch latch = new CountDownLatch(330);
for(int i=0;i<330;i++){
// 自定义执行体
MyTask myTask = new MyTask(i,latch);
executor.execute(myTask);
System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
executor.getQueue().size()+",已执行玩别的任务数目:"+executor.getCompletedTaskCount());
}
// 控制所有线程全部跑完(异步执行可以去掉)
latch.await();
long s2 = System.currentTimeMillis();
long s =s2-s1;
System.out.println("线程池执行时间:"+s);
System.out.println("执行完毕=============================");
executor.shutdown();
}
}
class MyTask implements Runnable {
private int taskNum;
private CountDownLatch latch;
public MyTask(int num,CountDownLatch latch) {
this.taskNum = num;
this.latch = latch;
}
@Override
public void run() {
System.out.println("正在执行task "+taskNum);
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
latch.countDown();
}
System.out.println("task "+taskNum+"执行完毕");
}
}
二、ForkJoinPool
Fork/Join框架是Java 7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。Fork/Join框架要完成两件事情:
1.任务分割:首先Fork/Join框架需要把大的任务分割成足够小的子任务,如果子任务比较大的话还要对子任务进行继续分割
2.执行任务并合并结果:分割的子任务分别放到双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都放在另外一个队列里,启动一个线程从队列里取数据,然后合并这些数据。
在Java的Fork/Join框架中,使用两个类完成上述操作
1.ForkJoinTask:我们要使用Fork/Join框架,首先需要创建一个ForkJoin任务。该类提供了在任务中执行fork和join的机制。通常情况下我们不需要直接集成ForkJoinTask类,只需要继承它的子类,Fork/Join框架提供了两个子类:
a.RecursiveAction:用于没有返回结果的任务
b.RecursiveTask:用于有返回结果的任务
2.ForkJoinPool:ForkJoinTask需要通过ForkJoinPool来执行
任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务(工作窃取算法)。
Fork/Join框架的实现原理
ForkJoinPool由ForkJoinTask数组和ForkJoinWorkerThread数组组成,ForkJoinTask数组负责将存放程序提交给ForkJoinPool,而ForkJoinWorkerThread负责执行这些任务。
ForkJoinPool简单示例
package constant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.TimeUnit;
public class ForkJoinTask extends RecursiveTask<List<String>> {
/**
*
* @author: FangRen
* @version V2.0
* @date: 2018年7月2日 下午4:55:15
*/
private static final long serialVersionUID = 1L;
private int THRESHOLD = 2;
private int start;
private int end;
public ForkJoinTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected List<String> compute() {
List<String> list = new ArrayList<>();
//如果任务足够小就计算任务
boolean canCompute = (end - start) <= THRESHOLD;
if (canCompute) {
for (int i = start; i < end; i++) {
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程池中线程数目:"+ForkJoinTask.getPool().getPoolSize()+",队列中等待执行的任务数目:"+
ForkJoinTask.getPool().getQueuedTaskCount());
list.add("wo");
}
return list;
}
int middle = (end + start) / 2;
ForkJoinTask task1 = new ForkJoinTask(start, middle);
ForkJoinTask task2 = new ForkJoinTask(middle, end);
invokeAll(task1, task2);
//等待任务执行结束合并其结果
List<String> leftResult = task1.join();
List<String> rightResult = task2.join();
list.addAll(rightResult);
list.addAll(leftResult);
return list;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
List<String> list = new ArrayList<String>();
for(int i=1;i<=330;i++){
list.add("i------"+i);
}
System.out.println(list.size());
long s1 = System.currentTimeMillis();
// 参数默认是cpu数(可以自定义)
ForkJoinPool pool = new ForkJoinPool();
// 提交可分解的PrintTask任务
Future<List<String>> ss = pool.submit(new ForkJoinTask(0,list.size()));
//线程阻塞,等待所有任务完成
pool.awaitTermination(1, TimeUnit.SECONDS);
// 关闭线程池
pool.shutdown();
long s2 = System.currentTimeMillis();
long s =s2-s1;
System.out.println(s);
}
}