Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序 都可以使用线程池。在开发过程中,合理地使用线程池能够带来3个好处.
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源, 还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用 线程池,必须对其实现原理了如指掌。
线程池的创建方式
总共有7种,总体来说分为2类:
1. 通过 ThreadPoolExecutor 创建的线程池;
2. 通过 Executors 创建的线程池。
线程池创建方式
- Executors.newFixedThreadPool:创建⼀个固定⼤⼩的线程池,可控制并发的线程数,超出的线程会在队列中等待;
- Executors.newCachedThreadPool:创建⼀个可缓存的线程池,若线程数超过处理所需,缓存⼀段时间后会回收,若线程数不够,则新建线程;
- Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执⾏顺序;
- Executors.newScheduledThreadPool:创建⼀个可以执⾏延迟任务的线程池;
- Executors.newSingleThreadScheduledExecutor:创建⼀个单线程的可以执⾏延迟任务的线程池;
- Executors.newWorkStealingPool:创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK1.8 添加】。
- ThreadPoolExecutor:最原始的创建线程池的⽅式,它包含了 7 个参数可供设置,后⾯会详细讲。
创建固定大小线程池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService service = Executors.newFixedThreadPool(3);
Future<?> submit = service.submit(new Runnable() { @Override public void run() { System.out.println("Runnable创建方式" + Thread.currentThread().getName()); } });
service.execute(new Thread(() -> { System.out.println("Thread创建方式" + Thread.currentThread().getName()); }));
Future<Integer> submit1 = service.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { System.out.println("Callable创建方式" + Thread.currentThread().getName()); int i = new Random().nextInt(); return i; } });
System.out.println("返回结果:" + submit1.get());
}
|
创建可缓存的线程池,感觉线程任务数量创建
- 优点:在一定时间内可以重复使用这些线程,产生相应的线程池。
- 缺点:适用于短时间有大量任务的场景,它的缺点是可能会占用很多的资源。
1 2 3 4 5 6 7
| ExecutorService service = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { int finailI = i; service.submit(() -> { System.out.println("i:" + finailI + "| 线程名称: " + Thread.currentThread().getName()); }); }
|
创建延时定时任务线程池
scheduleAtFixedRate 是以上⼀次任务的开始时间,作为下次定时任务的参考时间的(参考时间+延迟任务=任务执⾏)。*
scheduleWithFixedDelay 是以上⼀次任务的结束时间,作为下次定时任务的参考时间的。*
schedule():可以设置延时时间,定时执行;
1 2 3 4 5 6 7 8 9
| public static void main(String[] args) { System.out.println("执行任务:" + LocalDateTime.now());
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5); pool.schedule(() -> { System.out.println("执行子任务:" + LocalDateTime.now());
}, 3, TimeUnit.SECONDS); }
|
创建单线程线程池
1 2 3 4 5 6 7 8 9
| ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(); System.out.println("添加任务时间:" + LocalDateTime.now()); for (int i = 0; i < 10; i++) { int finalI = i; service.submit(() -> { System.out.println("线程名: " + Thread.currentThread().getName() + " " + finalI); }); }
|
创建可抢占式线程池
1 2 3 4 5 6 7 8 9 10 11 12 13
| ExecutorService service = Executors.newWorkStealingPool();
for (int i = 0; i < 10; i++) { int finalI = i; service.submit(() -> { System.out.println("线程名:" + Thread.currentThread().getName() + " " + finalI); });
}
while (!service.isTerminated()) { }
|
使用ThreadPoolExecutor创建
ThreadPoolExecutor参数说明
拒绝策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public static void main(String[] args) { ThreadFactory factory = new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName("线程:" + thread.getName()); return thread; } };
ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, 5, 100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1), factory, new ThreadPoolExecutor.CallerRunsPolicy() );
for (int i = 0; i < 10; i++) { int finalI = i; executor.submit(() -> { System.out.println(Thread.currentThread().getName() + "执行任务:" + finalI); }); }
executor.shutdown();
}
|
执行流程
- 线程总数量 < corePoolSize,无论线程是否空闲,都会新建一个核心线程执行任务(让核心线程数量快速达到corePoolSize,在核心线程数量 < corePoolSize时)。注意,这一步需要获得全局锁。
- 线程总数量 >= corePoolSize时,新来的线程任务会进入任务队列中等待,然后空闲的核心线程会依次去缓存队列中取任务来执行(体现了线程复用)。
- 当缓存队列满了,说明这个时候任务已经多到爆棚,需要一些“临时工”来执行这些任务了。于是会创建非核心线程去执行这个任务。注意,这一步需要获得全局锁。
- 缓存队列满了, 且总线程数达到了maximumPoolSize,则会采取上面提到的拒绝策略进行处理。