-
为什么需要线程池
当程序需要访问Web服务器,数据库服务器等远程资源时,单词请求的处理时间都很短吗,如果为每个请求都创建一个新的线程,则会频繁的创建和销毁线程,这个过程带来的系统开销是巨大的。
线程池为线程生命周期开销问题和资源不足问题提供了解决方案,通过让多个任务重用线程,线程创建的开销被分摊到多个任务上。其好处是,因为请求到达时线程已经存在,带来了线程创建时的延迟,可以立即为请求服务,是应用程序响应更快。并且,通过适当的调节线程池中线程的数量,当请求的数量超过设置的线程数量的时候,就强制其他新到的请求进入等待状态,直到获得一个线程处理为止,从而防止资源不足。
-
风险与机遇:
用线程池构建的应用程序容易遭受任何其他多线程应用程序遭受的所有并发风险,诸如同步错误和死锁,还容易遭受线程池的少数其他风险,诸如与线程池有关的死锁,资源不足和线程泄露。
-
线程池的创建及使用代码示例
import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * 线程池的创建及使用 */ public class ThreadPoolDemo { public static void main(String[] args) { LinkedBlockingDeque<Runnable> runnableQueue = new LinkedBlockingDeque<>(); ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20, 3L, TimeUnit.SECONDS, runnableQueue); for (int i = 0; i < 100; i++) { threadPoolExecutor.submit(() -> { try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); }); } } }
-
Callable与Runnable功能相似,Callable的call有返回值,可以返回给客户端,而Runnable没有返回值。一般情况下吗,Callable与FutureTask一起使用,或者通过线程池的submit方法返回相应的future。
-
Future就是对于具体的Callable或Runnable任务的执行结果进行取消、查询是否完成、获取结果、设置结果操作。get方法会阻塞,知道任务返回结果。
-
FutureTask则是一个RunnableFuture,而RunnableFuture实现了Runnable接口又实现了Future和两个接口。
-
Callable代码示例
import java.util.concurrent.*; public class CallableDemo implements Callable<String> { public static void main(String[] args) throws ExecutionException, InterruptedException { CallableDemo callableDemo = new CallableDemo(); //在FutureTask中使用 FutureTask<String> stringFutureTask = new FutureTask<>(callableDemo); new Thread(stringFutureTask).start(); String s = stringFutureTask.get(); System.out.println("=====s======" + s); /* 在线程池中使用 LinkedBlockingDeque<Runnable> runnableQueue = new LinkedBlockingDeque<>(); ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20, 3L, TimeUnit.SECONDS, runnableQueue); Future<String> submit = null; for (int i = 0; i < 20; i++) { submit = threadPoolExecutor.submit(new CallableDemo()); } for (int i = 0; i < 20; i++) { System.out.println(submit.get()); } */ } @Override public String call() throws Exception { Thread.sleep(3000L); return "1111"; } }
-
线程池的核心组成部分
- corePoolSize 核心线程池大小
- maximumPoolSize 线程池最大容量
- keepAliveTime 当线程的数量大于核心线程的数量的时候,多余的空闲线程在终止之前等待新任务的最大时间
- unit 时间单位
- workQueue 工作队列
- threadFactory 线程工厂
- handler 拒绝策略
-
线程池运行机制:
- 通过new创建线程池的时候,需要调用prestartAllCoreThreads方法初始化核心线程,否则线程池中没有线程,即使工作队列中存在多个任务,也不会被执行。
- 当任务数小于等于线程池核心线程数时,只启动任务数个线程。
- 当任务数大于等于线程池核心线程数时 且 小于工作队列的数 加 线程池的核心线程数时,此时会启动核心线程数个线程来执行队列中的线程。
- 当任务数大于工作队列的数加线程池的核心线程数时 且 任务数减去队列数 小于等于 线程池的最大容量时, 会启动最大容量个线程来执行任务。
- 任务数减去队列数 大于 线程池的最大容量时,会启东最大容量个线程来执行任务,其余的执行响应的拒绝策略。
-
线程池拒绝策略:
-
AbortPolicy 该策略直接抛出异常,阻止系统正常工作;
-
CallerRunsPolicy 只要线程池没有关闭,该策略直接在调用者线程中,执行当前被丢弃的任务(叫老板伴你干活);
-
DiscardPolicy 直接忽略被丢弃的任务;
-
DiscardOldestPolicy 丢弃最老的任务(任务队列里面的第一个),再尝试提交任务。
-
自定义丢弃策略
import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadPoolExecutor; public class CustomerPolicy implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println(Thread.currentThread().getName() + "线程池已满"); } }
-
调用上面的丢弃策略
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20, 3L, TimeUnit.SECONDS, runnableQueue, new CustomerPolicy());
-
Executors框架
-
Executors创建6中线程池,下面的方法都创建了ThreadPoolExecutor,
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; /** * Executor框架Demo */ public class ExecutorDemo { public static void main(String[] args) { //创建了一个可以根据需要创建新线程的线程池,如果有空闲的线程,优先使用空闲的线程 ExecutorService executorService = Executors.newCachedThreadPool(); //创建一个固定大小的线程池(任何时候都是这么多个线程),等待队列为int最大值的的线程池 ExecutorService executorService1 = Executors.newFixedThreadPool(2); //能延时执行、定时执行的定时任务 ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5); //工作窃取,使用多个队列来减少竞争 ExecutorService executorService2 = Executors.newWorkStealingPool(); //单一线程的线程池,只会使用一个线程来执行任务 ExecutorService executorService3 = Executors.newSingleThreadExecutor(); //单线程的能延时执行、定时执行的线程池 ScheduledExecutorService scheduledExecutorService2 = Executors.newSingleThreadScheduledExecutor(); executorService.submit(() -> { System.out.println(Thread.currentThread().getName()); }); } }
-
线程池的使用建议:
- 尽量避免使用Executors框架创建线程池;
- newFixedThreadPool、newSingleThreadExecutor允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM;
- newCachedThreadPool、newScheduledThreadPool允许创建的线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM;
- 创建线程池时,核心线程的数量不要过大。
-
submit:执行线程池任务的时候,如果发生异常,不会立即抛出,而是在get的时候再抛出响应的异常
-
execute:在执行线程池任务的时候,会立即抛出相关的异常