我们使用线程的时候就去创建一个线程, 但是就会有一个问题:
如果并发的线程数量非常多, 而且每个线程都是执行一个时间很短的任务就结束了, 这样频繁创建线程就会导致大大降低系统的效率, 因为频繁创建线程和销毁线程需要时间
那么有没有一种办法使得线程可以复用, 就是执行完一个任务, 并不被销毁, 而是可以继续执行其他的任务?
线程池正好能解决这样的问题正如名称所称的那样, 线程池管理一个工作者线程的同构池线程池是与工作队列紧密绑定的所谓工作队列, 其作用是持有所有等待执行的任务
工作者线程的生活从此轻松起来: 它从工作队列中获取下一个任务, 执行它, 然后回来等待另外一个线程
这类似于企业应用程序中事务监听器 (transaction monitor) 的角色: 它将课运行事务的数量控制在一个合理的水平中, 不会因过渡滥用事务而耗尽有限资源
线程池中执行任务线程, 这方法有很多每任务每线程无法笔记的优势重用存在的线程, 而不是创建新的线程, 这可以在处理多请求时抵消线程创建, 消亡产生的开销还有一个好处就是, 在请求到达时, 工作者线程通常已经存在
, 用于创建线程的等待时间并不会延迟任务的执行, 因此提高响应性通过适当地调整线程池的大小, 你可以得到足够多的线程以保持处理器忙碌, 同时可以还防止过多的线程互相竞争资源, 导致应用程序耗尽内存或者失败
每任务每线程例子如下:
- public class ThreadPool {
- public static void main(String[] args) throws IOException {
- ServerSocket serverSocket = new ServerSocket(80);
- while (true){
- final Socket socket = serverSocket.accept();
- Runnable task = new Runnable() {
- public void run() {
- handleRequest(socket);
- }
- };
- new Thread(task).start();
- }
- }
- private static void handleRequest(Socket socket) {
- }
- }
可以看到这个例子是一个粗制滥造的并发服务端, 来一个用户就创建一个线程, 你根本就不知道有多少用户来, 要创建多少个线程这样频繁创建线程就会导致大大降低系统的效率, 因为频繁创建线程和销毁线程需要时间,
过渡滥创建线程而耗尽有限资源
由于这原因, java 中给我们提供 Executor 框架通过 Executors 中的某个静态工厂方法来创建一个线程池:
1.newFixedThreadPool 创建一个定长的线程池, 每当提交一个任务就创建一个线程, 知道达到池的最大长度, 这时线程池会保持长度不再变化(如果一个线程由于非预期的 Exception 而结束, 线程池会补充一个新的线程)
下面用 newFixedThreadPool 创建一个定长的线程池来改造上面的例子, 如下:
- public class ThreadPool {
- public static void main(String[] args) throws IOException {
- //newFixedThreadPool 参数为线程池的大小
- Executor executor = Executors.newFixedThreadPool(100);
- ServerSocket serverSocket = new ServerSocket(80);
- while (true){
- final Socket socket = serverSocket.accept();
- Runnable task = new Runnable() {
- public void run() {
- handleRequest(socket);
- }
- };
- // 直接将任务丢进线程池来执行任务
- executor.execute(task);
- }
- }
- private static void handleRequest(Socket socket) {
- }
- }
这样就不会发生过渡滥创建线程而耗尽有限资源
2.newSingleThreadExecutor 创建一个单线程化的 executor, 他只创建唯一的工作者线程来执行任务, 如果这个线程异常结束, 会有另一个取代它 executor 会保证任务依照任务队列所规定的顺序 (FIFO,LIFO, 优先级) 执行
3.newCachedThreadPool 创建一个可缓存的线程池, 如果当前线程的长度超过了处理的需要, 它可以灵活的回收空闲的线程, 当需求增加时, 它可以灵活的增加新的线程, 并不会对池的长度做任何限制但是认为改线程池的长度没有任何限制, 有可能会把资源耗尽,
这需要自己很好的把控了
4.newScheduledThreadPool 创建一个定长的线程池, 而且支持定时的以及周期性的任务执行, 类似于 Timer
Executor 的生命周期:
Executor 实现通常知识为执行任务而创建线程但是 JVM 会在所有 (非后台的, nondaemon) 线程全部终止后才退出因此, 如果无法正确关闭 Executor, 将会阻止 JVM 的结束
因为 Executor 是异步地执行任务, 所以在任何时间里, 所有之前提交的任务状态都不能立即可见这些任务中, 有些可能已经完成, 有些可能正在运行, 其他的还可能在队列中等待执行关闭应用程序时, 程序会出现很多中情况: 从平缓关闭
到最突然的关闭, 以及介于这两种阶段情况之间的各种可能 Executor 是为应用服务提供服务的, 他们理应可以关闭, 无论是平缓还是突然
注意: 关闭操作还会影响到记录应用程序任务状态的反馈信息
Executor 就是一个接口, 源码如下图:
我们可以进入 Executors 这个类的源码, 如下:
可以看到 newFixedThreadPool 创建一个定长的线程池, 返回的是一个 ExecutorService, 但是我们上面例子接收的是 Executor, 为什么 Executor 也可以接收呢? 我们继续进入 ExecutorService 源码如下:
可以看到原来 ExecutorService 继承了 ExecutorExecutorService 扩展了 Executor, 并且添加了一些用于声明周期管理的方法
源码如下:
ExecutorService 暗示了生命周期有 3 种状态: 运行关闭终止 ExecutorService 最初创建后的初始状态是运行状态
shutdown 方法会启动一个平缓的关闭过程: 停止接受新的任务, 同时等待已经提交的任务完成, 包括尚未开始执行的任务
shutdownNow 方法会启动要给强制的关闭过程: 尝试取消所有运行中的任务和排在队列中尚未开始执行的任务 is
isShutdown 方法: 判断线程池 (即 ExecutorService) 是否关闭
isTerminated 方法: 是线程池 (即 ExecutorService) 是否进入终止状态
在关闭后提交到 ExecutorService 中的任务, 会被拒绝执行处理器 (rejected execution handler) 处理拒绝执行处理器是 ExecutorService 的一种实现, ThreadPoolExecutor 提供的, ExecutorService 接口中的方法并不提供拒绝执行处理器拒绝执行处理器可能只是
简单的放弃任务, 也可能会引起 execute 抛出一个未检查的 RejectedExecutionException 一旦所有任务全部完成后, ExecutorService 会转入终止状态
awaitTermination 方法: 等待 ExecutorService 到达终止状态
通常 shutdown 会紧随 awaitTermination 之后, 这样可以产生同步地关闭 ExecutorService 的效果
上面的 Executor 的例子的程序是没办法关闭线程池, 会一直跑下去, 那么我们如何写一个支持关闭的 webserver 呢?
明显我们现在要用 ExecutorService 来改造上面的 Executor 的例子伪代码如下:
- public class ThreadPool {
- private static ExecutorService executorService = Executors.newCachedThreadPool();
- public static void main(String[] args) throws IOException {
- //newFixedThreadPool 参数为线程池的大小
- ServerSocket serverSocket = new ServerSocket(80);
- // 这里就不再像上面的例子一样无限的接受任务了, 要根据我的线程池是否处于关闭状态来决定
- while (!executorService.isShutdown()){
- final Socket socket = serverSocket.accept();
- try{
- executorService.execute(new Runnable() {
- public void run() {
- handleRequest(socket);
- }
- });
- }catch (RejectedExecutionException e){
- // 如果拒绝服务不是因为我线程池关闭导致的, 我们要在这里打印一下日志
- if (!executorService.isShutdown()){
- System.out.println("接受任务被拒绝");
- throw e;
- }
- }
- }
- }
- // 用一个公共的方法去关闭线程池
- public void stop(){
- executorService.shutdown();
- }
- private static void handleRequest(Socket socket) {
- // 获取请求
- Request req = readRequest(socket);
- // 如果请求已经关闭
- if (isShutdownRequest(req)){
- // 关闭线程池
- stop();
- }else {
- // 请求转发
- dispatchRequest(req);
- }
- }
- }
经过改造, 这服务端变的优雅多了
延时的, 并具有周期性的任务
在 newScheduledThreadPool 出来之前我们一般会用 Timer 和 TimerTask 来做 Timer 在 JDK 里面, 是很早的一个 API 了
但是 Timer 存在一些缺陷, Timer 只创建唯一的线程来执行所有 Timer 任务如果一个 timer 任务的执行很耗时, 会导致其他 TimerTask 的时效准确性出问题例如一个 TimerTask 每 10 秒执行一次,
而另外一个 TimerTask 每 40ms 执行一次, 重复出现的任务会在后市的任务完成后快速连续的被调用 4 次, 要么完全丢失 4 次调用
Timer 的另外一个问题在于, 如果 TimerTask 抛出未检查的异常会终止 timer 线程这种情况下, Timer 也不会重新回复线程的执行了; 它错误的认为整个 Timer 都被取消了此时
已经被安排但尚未执行的 TimerTask 永远不会再执行了, 新的任务也不能被调度了
现在我们看一下 Timer 的例子, 如下:
- public class Shedule {
- private static long start;
- public static void main(String[] args) {
- TimerTask task = new TimerTask() {
- public void run() {
- System.out.println(System.currentTimeMillis()-start);
- try{
- Thread.sleep(3000);
- }catch (InterruptedException e){
- e.printStackTrace();
- }
- }
- };
- TimerTask task1 = new TimerTask() {
- @Override
- public void run() {
- System.out.println(System.currentTimeMillis()-start);
- }
- };
- Timer timer = new Timer();
- start = System.currentTimeMillis();
- // 启动一个调度任务, 1S 钟后执行
- timer.schedule(task,1000);
- // 启动一个调度任务, 3S 钟后执行
- timer.schedule(task1,3000);
- }
- }
上面程序我们预想是第一个任务执行后, 第二个任务 3S 后执行的, 即输出一个 1000, 一个 3000.
实际运行结果如下:
实际运行结果并不如我们所愿世界结果, 是过了 4S 后才输出第二个任务, 即 4001 约等于 4 秒那部分时间时间到哪里去了呢? 那个时间是被我们第一个任务的 sleep 所占用了
现在我们在第一个任务中去掉 Thread.sleep(); 这一行代码, 运行是否正确了呢? 如下:
- public class Shedule {
- private static long start;
- public static void main(String[] args) {
- TimerTask task = new TimerTask() {
- public void run() {
- System.out.println(System.currentTimeMillis()-start);
- }
- };
- TimerTask task1 = new TimerTask() {
- @Override
- public void run() {
- System.out.println(System.currentTimeMillis()-start);
- }
- };
- Timer timer = new Timer();
- start = System.currentTimeMillis();
- // 启动一个调度任务, 1S 钟后执行
- timer.schedule(task,1000);
- // 启动一个调度任务, 3S 钟后执行
- timer.schedule(task1,3000);
- }
- }
运行结果如下:
可以看到确实是第一个任务过了 1S 后执行, 第二个任务在第一个任务执行完后过 3S 执行了
这就说明了 Timer 只创建唯一的线程来执行所有 Timer 任务如果一个 timer 任务的执行很耗时, 会导致其他 TimerTask 的时效准确性出问题
Timer 存在一些缺陷, 因此你应该考虑使用 ScheduledThreadPoolExecutor 作为替代品你可以通过构造函数或者通过 newScheduledThreadPool 工厂方法创建一个 ScheduledThreadPoolExecutor
如下:
- public class Shedule {
- private static long start;
- private static ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
- public static void main(String[] args) {
- TimerTask task = new TimerTask() {
- public void run() {
- System.out.println(System.currentTimeMillis()-start);
- try{
- Thread.sleep(3000);
- }catch (InterruptedException e){
- e.printStackTrace();
- }
- }
- };
- TimerTask task1 = new TimerTask() {
- @Override
- public void run() {
- System.out.println(System.currentTimeMillis()-start);
- }
- };
- Timer timer = new Timer();
- start = System.currentTimeMillis();
- //TimeUnit.MILLISECONDS 指定毫秒为单位
- executorService.schedule(task,1000, TimeUnit.MILLISECONDS);
- executorService.schedule(task1,3000, TimeUnit.MILLISECONDS);
- }
- }
运行结果如下:
可以看到运行结果符合预期
可以看到如果一个 timer 任务的执行很耗时(例如 Thread.sleep),ScheduledThreadPoolExecutor 并不会导致其他 TimerTask 的时效准确性出问题
还可以看到, 这两个 TimerTask 互不干扰
互相干扰还有一个反面:
如果 TimerTask 抛出未检查的异常会终止 timer 线程这种情况下, Timer 也不会重新回复线程的执行了; 它错误的认为整个 Timer 都被取消了此时
已经被安排但尚未执行的 TimerTask 永远不会再执行了, 新的任务也不能被调度了
例子如下:
- public class Shedule {
- private static long start;
- private static ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
- public static void main(String[] args) {
- TimerTask task = new TimerTask() {
- public void run() {
- System.out.println(System.currentTimeMillis()-start);
- throw new RuntimeException();
- }
- };
- TimerTask task1 = new TimerTask() {
- @Override
- public void run() {
- System.out.println(System.currentTimeMillis()-start);
- }
- };
- Timer timer = new Timer();
- start = System.currentTimeMillis();
- timer.schedule(task,1000);
- timer.schedule(task1,3000);
- }
- }
如果第一 TimerTask 出现未知异常, 第二个 TimerTask 还能运行起来吗?
结果如下:
明显第一 TimerTask 出现未知异常, 第二个 TimerTask 不能运行起来了这就说明
如果 TimerTask 抛出未检查的异常会终止 timer 线程这种情况下, Timer 也不会重新回复线程的执行了; 它错误的认为整个 Timer 都被取消了此时
已经被安排但尚未执行的 TimerTask 永远不会再执行了, 新的任务也不能被调度了
ScheduledThreadPoolExecutor 可以解决此问题, 例子如下:
- public class Shedule {
- private static long start;
- private static ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
- public static void main(String[] args) {
- TimerTask task = new TimerTask() {
- public void run() {throw new RuntimeException();
- }
- };
- TimerTask task1 = new TimerTask() {
- @Override
- public void run() {
- System.out.println(System.currentTimeMillis()-start);
- }
- };
- Timer timer = new Timer();
- start = System.currentTimeMillis();
- //TimeUnit.MILLISECONDS 指定毫秒为单位
- executorService.schedule(task,1000, TimeUnit.MILLISECONDS);
- executorService.schedule(task1,3000, TimeUnit.MILLISECONDS);
- }
- }
运行结果如下:
可以看到第一个线程挂了, 第二个线程并没有受到影响这就说明了 ScheduledThreadPoolExecutor 可以解决了
如果 TimerTask 抛出未检查的异常会终止 timer 线程这种情况下, Timer 也不会重新回复线程的执行了; 它错误的认为整个 Timer 都被取消了此时
已经被安排但尚未执行的 TimerTask 永远不会再执行了, 新的任务也不能被调度了的问题
还要注意一点, Timer 是和系统时间挂钩的, 如果当前服务器的时间一改, Timer 就不那么靠谱了
还要注意的是 ThreadPoolExecutor
源码如下图:
可以看到 new 一个 FixedThreadPool/newSingleThreadExecutor/newCachedThreadPool/newScheduledThreadPool 实际上返回的都是是 new ThreadPoolExecutor()
我们再看一下 ThreadPoolExecutor 源码如下:
可以看到 ThreadPoolExecutor 配置的非常灵活, 如果我们用普通的一个 FixedThreadPool/newSingleThreadExecutor/newCachedThreadPool/newScheduledThreadPool 没办法满足你的需求了, 你可以用
ThreadPoolExecutor 灵活的指定参数来完成你的需求这适合精确的任务执行还不如说我们的任务被拒绝 (RejecedExecutionHandler) 后, 我们可以用 ThreadPoolExecutor 灵活处理
来源: https://www.cnblogs.com/huangjuncong/p/8531281.html