Java 线程池

来源:互联网 发布:linux下没有.ssh目录 编辑:程序博客网 时间:2024/06/06 03:05

线程池

 程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池。
 线程是稀缺资源,如果被无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,合理的使用线程池对线程进行统一分配、调优和监控,有以下好处:
 1、降低资源消耗;
 2、提高响应速度;
 3、提高线程的可管理性。


线程池实现原理

当一个新任务提交到线程池后,处理流程如下:

1、线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务;如果核心线程池已满,则进入下一步。

2、线程池判断工作队列是否已满。如果工作队列未满,则将新任务存储于工作队列中。如果工作队列也满了,则进入下一步。

3、线程池判断线程池中的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务,如果线程池已满,则交给饱和策略处理该任务。
这里写图片描述

ThreadPoolExecutor执行execute方法分下面4种情况。
1)如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤
需要获取全局锁)。
2)如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue等待。
3)如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁)。
4)如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用 RejectedExecutionHandler.rejectedExecution()方法。
这里写图片描述
线程池一般由两种角色构成:多个工作线程 和 一个阻塞队列。

1)工作线程
 工作线程是一组已经处在运行中的线程,它们不断地向阻塞队列中领取任务执行。
2)阻塞队列
 阻塞队列用于存储工作线程来不及处理的任务。当工作线程都在执行任务时,到来的新任务就只能暂时在阻塞队列中存储。


ThreadPoolExecutor的使用

(1)我们可以通过ThreadPoolExecutor来创建一个线程池。

new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, timeUnit, runnableTaskQueue, handler);

1)corePoolSize:线程池的基本大小
它表示你希望线程池达到的一个值。线程池会尽量把实际线程数量保持在这个值上下。当提交一个任务时,线程池创建一个新线程执行任务,就算空闲线程可以执行该任务也一样会创建新线程,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行。

2)maximumPoolSize:最大线程数量
这是线程数量的上界。 如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize。如果超过可maximumPoolSize就会调用饱和策略

3)keepAliveTime
线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时间;默认情况下,该参数只在线程数大于corePoolSize时才有用;

4)RejectExecutionHandler
线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:
 1、AbortPolicy:直接抛出异常,默认策略;
 2、CallerRunsPolicy:用调用者所在的线程来执行任务;
 3、DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
 4、DiscardPolicy:直接丢弃任务;

5)runnableTaskQueue:任务队列
这是一个存放任务的阻塞队列,可以有如下几种选择:
 1.ArrayBlockingQueue :它是一个由数组实现的阻塞队列,FIFO。
 2.LinkedBlockingQueue :它是一个由链表实现的阻塞队列,FIFO。 吞吐量通常要高于ArrayBlockingQueue,fixedThreadPool使用的阻塞队列就是它。 它是一个无界队列。
 3.SynchronousQueue :它是一个没有存储空间的阻塞队列,任务提交给它之后必须要交给一条工作线程处理;如果当前没有空闲的工作线程,则立即创建一条新的工作线程。 cachedThreadPool用的阻塞队列就是它。 它是一个无界队列。
 4.PriorityBlockingQueue :它是一个优先权阻塞队列。如果优先级高的任务一直提交到队列里,那么优先级低的任务就不可能执行。
建议使用有界队列,因为如果使用无界队列,出了问题比如线程全部阻塞,任务挤压在线程池里,那么队列里的等待的任务会越来越多,撑满内存导致系统崩溃。

(2)向线程池提交任务
可以使用execute()和submit()两种方法向线程池提交任务。

1)execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。 通过以下代码可知execute()方法输入的任务是一个Runnable类的实例,必须实现Runnable接口。

threadsPool.execute(new Runnable() {                                    @Override                                    public void run() {                                            // TODO Auto-generated method stub             }                     });

2)submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个 future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值。

Future<Object> future = executor.submit(harReturnValuetask);                           try {                                        Object s = future.get();                          } catch (InterruptedException e) {                                    // 处理中断异常                          } catch (ExecutionException e) {                                      // 处理无法执行任务异常                        } finally {                                          // 关闭线程池                                        executor.shutdown();                }

(3)关闭线程池
 关闭线程池有两种方式:shutdown和shutdownNow,关闭时,会遍历所有的线程,调用它们的interrupt函数中断线程。但这两种方式对于正在执行的线程处理方式不同,shutdownNow首先将线程池的状态设置成 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,而 shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
 通常调用shutdown方法来关闭 线程池,如果任务不一定要执行完,则可以调用shutdownNow方法。

0 0
原创粉丝点击