- 什么是线程池?
- 答:一个线程池管理了一组工作线程,同时它还包括了一个用于放置等待执行的任务的队列。
- 线程池的作用
- 答:
- 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
- Java中的线程池种类
- 答:Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。
- newSingleThreadExecutor
- 创建方式:
ExecutorService pool = Executors.newSingleThreadExecutor();
- 一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
- 使用方式:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPool { public static void main(String[] args) { ExecutorService pool = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { pool.execute(() -> { System.out.println(Thread.currentThread().getName() + "\t开始发车啦...."); }); } } }
- 输出结果如下:
- 从输出的结果我们可以看出,一直只有一个线程在运行。
- newFixedThreadPool
- 创建方式:
ExecutorService pool = Executors.newFixedThreadPool(10);
- 创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
- 使用方式:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPool { public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(10); for (int i = 0; i < 10; i++) { pool.execute(() -> { System.out.println(Thread.currentThread().getName() + "\t开始发车啦...."); }); } } }
- 输出结果如下:
- newCachedThreadPool
- 创建方式:
ExecutorService pool = Executors.newCachedThreadPool();
- 创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲的线程,当任务数增加时,此线程池又添加新线程来处理任务。
- 使用方式:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPool { public static void main(String[] args) { ExecutorService pool = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { pool.execute(() -> { System.out.println(Thread.currentThread().getName() + "\t开始发车啦...."); }); } } }
- 使用方式:
- newScheduledThreadPool
- 创建方式:
ScheduledExecutorService pool = Executors.newScheduledThreadPool(10);
- 此线程池支持定时以及周期性执行任务的需求。
- 使用方式:
import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class ThreadPool { public static void main(String[] args) { ScheduledExecutorService pool = Executors.newScheduledThreadPool(10); for (int i = 0; i < 10; i++) { pool.schedule(() -> { System.out.println(Thread.currentThread().getName() + "\t开始发车啦...."); }, 10, TimeUnit.SECONDS); } } }
- 上面演示的是延迟10秒执行任务,如果想要执行周期性的任务可以用下面的方式,每秒执行一次。
//pool.scheduleWithFixedDelay也可以 pool.scheduleAtFixedRate(() -> { System.out.println(Thread.currentThread().getName() + "\t开始发车啦...."); }, 1, 1, TimeUnit.SECONDS);
- newWorkStealingPool
- newWorkStealingPool是jdk1.8才有的,会根据所需的并行层次来动态创建和关闭线程,通过使用多个队列减少竞争,底层用的ForkJoinPool来实现的。ForkJoinPool的优势在于,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行;当多个“小任务”执行完成之后,再将这些执行结果合并起来即可。
- Java线程池中submit() 和 execute()方法有什么区别?
答:两个方法都可以向线程池提交任务
- execute() 方法的返回类型是void,它定义在Executor接口中。
- submit() 方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,适用于需要处理返回着或者异常的业务场景。其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。
- 通过executor.submit提交一个线程,返回一个Future,然后通过这个Future的get方法取得返回值。
- 在调用submit提交任务之后,主线程本来是继续运行了。但是运行到future.get()的时候就阻塞住了,一直等到任务执行完毕,拿到了返回的返回值,主线程才会继续运行。
- 这里注意一下,他的阻塞性是因为调用get()方法时,任务还没有执行完,所以会一直等到任务完成,形成了阻塞。
- 任务是在调用submit方法时就开始执行了,如果在调用get()方法时,任务已经执行完毕,那么就不会造成阻塞。
- 五种线程池的使用场景
- 答:
- newSingleThreadExecutor:一个单线程的线程池,可以用于需要保证顺序执行的场景,并且只有一个线程在执行。
newFixedThreadPool:一个固定大小的线程池,可以用于已知并发压力的情况下,对线程数做限制。
newCachedThreadPool:一个可以无限扩大的线程池,比较适合处理执行时间比较小的任务。
newScheduledThreadPool:可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。
newWorkStealingPool:一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。
线程池的关闭
答:关闭线程池可以调用shutdownNow和shutdown两个方法来实现
shutdownNow:对正在执行的任务全部发出interrupt(),停止执行,对还未开始执行的任务全部取消,并且返回还没开始的任务列表
public class ThreadPool { public static void main(String[] args) throws Exception { ExecutorService pool = Executors.newFixedThreadPool(1); for (int i = 0; i < 5; i++) { System.err.println(i); pool.execute(() -> { try { Thread.sleep(30000); System.out.println("--"); } catch (Exception e) { e.printStackTrace(); } }); } Thread.sleep(1000); List<Runnable> runs = pool.shutdownNow(); } }
上面的代码模拟了立即取消的场景,往线程池里添加5个线程任务,然后sleep一段时间,线程池只有一个线程,如果此时调用shutdownNow后应该需要中断一个正在执行的任务和返回4个还未执行的任务,控制台输出下面的内容:
shutdown:当我们调用shutdown后,线程池将不再接受新的任务,但也不会去强制终止已经提交或者正在执行中的任务。
public class ThreadPool { public static void main(String[] args) throws Exception { ExecutorService pool = Executors.newFixedThreadPool(1); for (int i = 0; i < 5; i++) { System.err.println(i); pool.execute(() -> { try { Thread.sleep(30000); System.out.println("--"); } catch (Exception e) { e.printStackTrace(); } }); } Thread.sleep(1000); pool.shutdown(); pool.execute(() -> { try { Thread.sleep(30000); System.out.println("--"); } catch (Exception e) { e.printStackTrace(); } }); } }
上面的代码模拟了正在运行的状态,然后调用shutdown,接着再往里面添加任务,肯定是拒绝添加的,请看输出结果:
0 1 2 3 4 Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task exercise.ThreadPool$$Lambda$2/455659002@eed1f14 rejected from java.util.concurrent.ThreadPoolExecutor@7229724f[Shutting down, pool size = 1, active threads = 1, queued tasks = 4, completed tasks = 0] at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(Unknown Source) at java.util.concurrent.ThreadPoolExecutor.reject(Unknown Source) at java.util.concurrent.ThreadPoolExecutor.execute(Unknown Source) at exercise.ThreadPool.main(ThreadPool.java:26) --
还有一些业务场景下需要知道线程池中的任务是否全部执行完成,当我们关闭线程池之后,可以用isTerminated来判断所有的线程是否执行完成,千万不要用isShutdown,isShutdown只是返回你是否调用过shutdown的结果。
package exercise; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPool { public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(1); for (int i = 0; i < 5; i++) { System.err.println(i); pool.execute(() -> { try { Thread.sleep(3000); System.out.println("--"); } catch (Exception e) { e.printStackTrace(); } }); } try { Thread.sleep(1000); } catch (Exception e2) { // TODO: handle exception } pool.shutdown(); while(true){ if(pool.isTerminated()){ System.out.println("所有的子线程都结束了!"); break; } try { Thread.sleep(1000); } catch (Exception e2) { // TODO: handle exception } } } }
显示结果:
Java基础:线程池
最新推荐文章于 2022-04-14 10:13:55 发布