什么是线程池?为什么要用线程池?
- 降低资源的消耗。降低线程创建和销毁的资源消耗;
- 提高响应速度:线程的创建时间为T1,执行时间T2,销毁时间T3,免去T1和T3的时间
- 提高线程的可管理性。
JDK中的线程池和工作机制
线程池的创建
ThreadPoolExecutor,jdk所有线程池实现的父类
各个参数含义
int corePoolSize :线程池中核心线程数,< corePoolSize ,就会创建新线程,= corePoolSize ,这个任务就会保存到BlockingQueue,如果调用prestartAllCoreThreads()方法就会一次性的启动corePoolSize 个数的线程。
int maximumPoolSize, 允许的最大线程数,BlockingQueue也满了,< maximumPoolSize时候就会再次创建新的线程
long keepAliveTime, 线程空闲下来后,存活的时间,这个参数只在> corePoolSize才有用
TimeUnit unit, 存活时间的单位值
BlockingQueue<Runnable> workQueue, 保存任务的阻塞队列
ThreadFactory threadFactory, 创建线程的工厂,给新建的线程赋予名字
RejectedExecutionHandler handler :饱和策略
- AbortPolicy :直接抛出异常,默认;
- CallerRunsPolicy:用调用者所在的线程来执行任务
- DiscardOldestPolicy:丢弃阻塞队列里最老的任务,队列里最靠前的任务
- DiscardPolicy :当前任务直接丢弃
提交任务
execute(Runnable command) 不需要返回
Future<T> submit(Callable<T> task) 需要返回
关闭线程池
shutdown(),shutdownNow();
shutdownNow():设置线程池的状态,还会尝试停止正在运行或者暂停任务的线程
shutdown()设置线程池的状态,只会中断所有没有执行任务的线程
工作机制
- 当线程数<corePoolSize,创建新线程
- 当线程数=corePoolSize ,想BlockingQueue中添加任务
- 当BlockingQueue已满,并且线程数<maxsize,创建线程
- 当BlockingQueue已满,并且线程数=maxsize,执行饱和策略
预定义的线程池
FixedThreadPool
ExecutorService pool = Executors.newFixedThreadPool(5);
创建固定线程数量的,适用于负载较重的服务器,使用了无界队列,
缺点:使用了无界队列,当任务过多时可能会导致OOM
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public class PoolTest {
public static void main(String[] args) throws InterruptedException {
// ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 10, 3000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(10), new ThreadPoolExecutor.DiscardOldestPolicy());
ExecutorService pool = Executors.newFixedThreadPool(5);
// ExecutorService pool = Executors.newSingleThreadExecutor();
// ExecutorService pool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
pool.execute(new MyWork(i));
}
for (int i = 0; i < 15; i++){
Thread.sleep(500);
System.out.println(System.currentTimeMillis());
}
}
static class MyWork implements Runnable {
int num;
MyWork(int num){
this.num = num;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is working "+num);
try {
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + " is done "+num);
} catch (Exception e) {
}
}
}
}
运行结果:
pool-1-thread-1 is working 0
pool-1-thread-2 is working 1
pool-1-thread-3 is working 2
pool-1-thread-4 is working 3
pool-1-thread-5 is working 4
1591088996392
pool-1-thread-1 is done 0
pool-1-thread-1 is working 5
pool-1-thread-2 is done 1
pool-1-thread-3 is done 2
pool-1-thread-3 is working 7
pool-1-thread-2 is working 6
pool-1-thread-4 is done 3
pool-1-thread-4 is working 8
pool-1-thread-5 is done 4
pool-1-thread-5 is working 9
1591088996892
pool-1-thread-1 is done 5
pool-1-thread-3 is done 7
pool-1-thread-2 is done 6
pool-1-thread-4 is done 8
pool-1-thread-5 is done 9
1591088997392
1591088997892
1591088998392
1591088998892
1591088999392
SingleThreadExecutor
ExecutorService pool = Executors.newSingleThreadExecutor();
创建单个线程,需要顺序保证执行任务,不会有多个线程活动,使用了无界队列
缺点:使用了无界队列,当任务过多时可能会导致OOM
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
运行结果:
pool-1-thread-1 is working 0
1591089156741
pool-1-thread-1 is done 0
pool-1-thread-1 is working 1
1591089157249
pool-1-thread-1 is done 1
pool-1-thread-1 is working 2
1591089157750
pool-1-thread-1 is done 2
pool-1-thread-1 is working 3
1591089158250
pool-1-thread-1 is done 3
pool-1-thread-1 is working 4
pool-1-thread-1 is done 4
1591089158750
pool-1-thread-1 is working 5
1591089159250
pool-1-thread-1 is done 5
pool-1-thread-1 is working 6
pool-1-thread-1 is done 6
1591089159750
pool-1-thread-1 is working 7
1591089160250
pool-1-thread-1 is done 7
pool-1-thread-1 is working 8
pool-1-thread-1 is done 8
1591089160750
pool-1-thread-1 is working 9
1591089161250
pool-1-thread-1 is done 9
1591089161750
1591089162250
1591089162750
CachedThreadPool
ExecutorService pool = Executors.newCachedThreadPool();
会根据需要来创建新线程的,执行很多短期异步任务的程序,使用了SynchronousQueu
缺点:max设置为Integer.MAX,则如果线程数过多,会导致崩溃
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
运行结果:
pool-1-thread-1 is working 0
pool-1-thread-2 is working 1
pool-1-thread-5 is working 4
pool-1-thread-6 is working 5
pool-1-thread-4 is working 3
pool-1-thread-8 is working 7
pool-1-thread-7 is working 6
pool-1-thread-9 is working 8
pool-1-thread-10 is working 9
pool-1-thread-3 is working 2
pool-1-thread-1 is done 0
1591089242278
pool-1-thread-2 is done 1
pool-1-thread-4 is done 3
pool-1-thread-5 is done 4
pool-1-thread-6 is done 5
pool-1-thread-8 is done 7
pool-1-thread-7 is done 6
pool-1-thread-9 is done 8
pool-1-thread-3 is done 2
pool-1-thread-10 is done 9
1591089242778
1591089243278
1591089243778
WorkStealingPool
基于ForkJoinPool实现
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool
(Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor service = new ScheduledThreadPoolExecutor(1);
需要定期执行周期任务,Timer不建议使用了。
newSingleThreadScheduledExecutor:只包含一个线程,只需要单个线程执行周期任务,保证顺序的执行各个任务
newScheduledThreadPool 可以包含多个线程的,线程执行周期任务,适度控制后台线程数量的时候
方法说明:
schedule:只执行一次,任务还可以延时执行
scheduleAtFixedRate:提交固定时间间隔的任务
scheduleWithFixedDelay:提交固定延时间隔执行的任务
scheduleAtFixedRate & scheduleWithFixedDelay 区别
scheduleAtFixedRate任务超时:
规定60s执行一次,有任务执行了80S,下个任务马上开始执行
第一个任务 时长 80s,第二个任务20s,第三个任务 50s
第一个任务第0s开始,第80S结束;
第二个任务第80s开始,在第100秒结束;
第三个任务第120s秒开始,170秒结束
第四个任务从180s开始
注意:当执行线程方法时,出现异常,会导致整个ScheduleTask异常,无法继续按期执行,建议在执行方法中try/catch异常
public class SchedulePoolTest {
public static void main(String[] args) throws InterruptedException {
ScheduledThreadPoolExecutor service = new ScheduledThreadPoolExecutor(1);
service.scheduleAtFixedRate(new MyWork(),0,6000, TimeUnit.MILLISECONDS);
for (int i = 0; i < 15; i++){
Thread.sleep(1000);
System.out.println(System.currentTimeMillis());
}
}
static class MyWork implements Runnable{
@Override
public void run() {
System.out.println("I am working");
}
}
}
执行结果:
I am working
1591090034596
1591090035596
1591090036596
1591090037596
1591090038596
1591090039596
I am working
1591090040596
1591090041596
1591090042596
1591090043597
1591090044597
1591090045597
I am working
1591090046597
合理配置线程池
根据任务的性质来:计算密集型(CPU),IO密集型,混合型
计算密集型:加密,大数分解,正则……., 线程数适当小一点,最大推荐:机器的Cpu核心数+1,为什么+1,防止页缺失,(机器的Cpu核心=Runtime.getRuntime().availableProcessors();)
IO密集型:读取文件,数据库连接,网络通讯, 线程数适当大一点,机器的Cpu核心数*2,
混合型:尽量拆分,IO密集型>>计算密集型,拆分意义不大,IO密集型~计算密集型
队列的选择上,应该使用有界,无界队列可能会导致内存溢出,OOM
CompletionService
/** * CompletionServcie的作用: * 当向线程池中添加多个任务时,可以根据各个任务的结束时间而获得结果, * 不会因为任务加入线程池顺序,而导致先执行完成的任务不能获取到结果 */
使用方式
ExecutorService service = Executors.newScheduledThreadPool(coreSize);
CompletionService<Long> cService = new ExecutorCompletionService<Long>(service);
..
cService.submit(..)
cService.take().get();
示例:
public class CompletionServiceTest {
private static Integer coreSize = Runtime.getRuntime().availableProcessors();
private static Integer taskSiz = Runtime.getRuntime().availableProcessors();
static class MyCallable implements Callable<Long> {
private String name;
MyCallable(String name) {
this.name = name;
}
@Override
public Long call() throws Exception {
long sleep = new Random().nextInt(500);
Thread.sleep(sleep);
System.out.println(name + " sleep " + sleep + "ms");
return sleep;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
test();
completionServiceTest();
}
private static void test() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ExecutorService service = Executors.newScheduledThreadPool(coreSize);
BlockingQueue<Future<Long>> queue = new LinkedBlockingQueue<>(taskSiz);
AtomicLong count = new AtomicLong();
for (int i = 0; i < taskSiz; i++) {
Future<Long> future = service.submit(new MyCallable("task" + i));
queue.add(future);
Thread.sleep(100);
}
for (Future<Long> future : queue) {
long sleep = queue.take().get();
count.addAndGet(sleep);
}
service.shutdown();
System.out.println("------------all sleep:" + count.get() + ", all time is:" + (System.currentTimeMillis() - start));
}
private static void completionServiceTest() throws InterruptedException, ExecutionException {
long start = System.currentTimeMillis();
ExecutorService service = Executors.newScheduledThreadPool(coreSize);
CompletionService<Long> cService = new ExecutorCompletionService<Long>(service);
AtomicLong count = new AtomicLong();
for (int i = 0; i < taskSiz; i++) {
cService.submit(new MyCallable("task" + i));
Thread.sleep(100);
}
// 检查线程池任务执行结果
for (int i = 0; i < taskSiz; i++) {
long sleptTime = cService.take().get();
count.addAndGet(sleptTime);
}
service.shutdown();
System.out.println("------------all sleep:" + count.get() + ", all time is:" + (System.currentTimeMillis() - start));
}
}
运行结果:
task0 sleep 374ms
task1 sleep 454ms
task2 sleep 383ms
task3 sleep 358ms
------------all sleep:1569, all time is:676
task0 sleep 60ms
task1 sleep 102ms
task2 sleep 311ms
task3 sleep 332ms
------------all sleep:805, all time is:635
根据结果可以看出,completionService是根据运行时间获得结果,不根据插入线程池的先后顺序。