为什么要使用线程池?
- 降低资源消耗:通过重复利用已经创建的线程减少线程创建和销毁带来的资源消耗。
- 提高响应速度:当任务到达时,不需要等待线程创建就可以直接执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制的创建的,不仅会消耗系统资源,还会降低系统稳定性,使用线程池可以进行统一的分配,调优和监控。
实现Runnable和Callable接口的区别?
Runnable接口一直存在,而Callable接口实在java 1.5 加入的,目的是为了处理Runnable接口不支持的用例。因为Runnable接口不支持返回结果和抛出异常,所以可以根据实际需要进行选择。
而 Executors 类是可以将这两种类型进行转换的。例如:
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
执行 execute() 方法和 submit() 方法的区别?
- execute() 方法用于提交不需要返回值的任务,所以就无法判断任务是否被线程池执行成功。
- submit() 方法用于提交需要返回值的任务,线程池会返回一个 Future 对象,可以通过这个对象判断线程是否执行成功。也可以调用 get() 方法获取返回值。
如何创建线程池?
通过Executors类创建:
- 创建一个固定数量的线程池:调用 newFixedThreadPool(int nThreads) 方法
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
将 corePoolSize 和 maximumPoolSize 设置相同的值,就是一个固定大小的线程池,另外加了一个无界的阻塞队列 LinkedBlockingQueue<Runnable>() ,所以就不会执行饱和策略,而线程池的实际线程数就一直是 nThreads ,所以空闲线程存活时间就没有意义了。
-
创建一个可缓存的线程池:调用 newCachedThreadPool() 方法
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
这是一个可以根据实际情况调整线程数量的线程池,空闲线程可以存活60s,而阻塞队列是没有存储空间的,所以如果没有空闲线程的话,就会创建一个线程执行,执行完毕返回线程池等待复用。
-
创建一个单线程化的 Executor
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
创建只有一条工作线程的线程池,如果有多余任务就会被保存到阻塞队列中,等待线程空闲按照先入先出的顺序执行。
-
创建一个可以用来处理延时或定时任务的线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); }
提交的任务会进入阻塞队列,按照执行时间排序,如果时间相同,就会根据任务的序号排序。
Executors 返回线程池对象的弊端如下:FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列⻓度为Integer.MAX_VALUE ,可能堆积⼤量的请求,从⽽导致 OOM 。CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为Integer.MAX_VALUE ,可能会创建⼤量线程,从⽽导致 OOM 。
ThreadPoolExecutor 饱和策略
定义:如果当前线程运行数量达到了最大线程数量并且队列也放满时,定义了一些策略。
-
ThreadPoolExecutor.AbortPolicy :抛出 RejectedExecutionException 来拒绝新任务的处理。
- ThreadPoolExecutor.CallerRunsPolicy :调用执行自己的线程执行任务,但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加 队列容量。如果您的应⽤程序可以承受此延迟并且你不能任务丢弃任何⼀个任务请求的话, 你可以选择这个策略。
- ThreadPoolExecutor.DiscardPolicy : 不处理新任务,直接丢弃掉。
- ThreadPoolExecutor.DiscardOldestPolicy : 此策略将丢弃最早的未处理的任务请求。
线程池原理
当提交一个任务时,先判断有效线程数量是否小于核心线程数量(corePoolSize),如果小于,就会创建线程执行任务;否则,就判断阻塞队列是否已满,队列满了的情况下,有效线程数如果小于最大线程数,就可以创建线程,如果都满了情况下,就会执行饱和策略。