什么叫线程池?线程池如何使用?
1、什么是线程池: java.util.concurrent.Executors提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池
多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。
假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。
如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。
一个线程池包括以下四个基本组成部分:
1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1,T3的开销了。
线程池不仅调整T1,T3产生的时间段,而且它还显著减少了创建线程的数目,看一个例子:
假设一个服务器一天要处理50000个请求,并且每个请求需要一个单独的线程完成。在线程池中,线程数一般是固定的,所以产生线程总数不会超过线程池中线程的数目,而如果服务器不利用线程池来处理这些请求则线程总数为50000。一般线程池大小是远小于50000。所以利用线程池的服务器程序不会为了创建50000而在处理请求时浪费时间,从而提高效率。
什么是线程池,如何使用,为什么要用
线程池,thread pool,是一种线程使用模式,线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。功能:应用程序可以有多个线程,这些线程在休眠状态中需要耗费大量时间来等待事件发生。其他线程可能进入睡眠状态,并且仅定期被唤醒以轮循更改或更新状态信息,然后再次进入休眠状态。为了简化对这些线程的管理,.NET框架为每个进程提供了一个线程池,一个线程池有若干个等待操作状态,当一个等待操作完成时,线程池中的辅助线程会执行回调函数。线程池中的线程由系统管理,程序员不需要费力于线程管理,可以集中精力处理应用程序任务。扩展资料:应用范围1、需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。2、对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。3、接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,并出现"OutOfMemory"的错误。参考资料来源:百度百科—线程池
什么是线程池?为什么要使用线程池?如何使用?
线程池其实就是将多个线程对象放到一个容器当中。
可以重用线程,减少创建和销毁线程带来的消耗。
要想知道如何使用线程池,就要先知道线程池的种类有多少种?线程池大概有以下几种:
以下介绍这几种线程池的用法:
ThreadPoolExecutor 是线程池真正的实现方法,以下是 ThreadPoolExecutor 的构造方法:
它需要传入一系列参数来配置线程池,下面介绍每个参数的意义。
一种固定线程数量的线程池。
可以通过 Executors 的 newFixedThreadPool() 方法创建:
newFixedThreadPool() 具体实现:
可以看出 newFixedThreadPool() 是通过创建 ThreadPoolExecutor 来创建线程池的。
并且因为 corePoolSize 和 maximumPoolSize 是一样的,所以这种线程池只有核心线程,任务超出线程数后,会在队列中等待。
具体使用如下:
一种线程数量不定的线程池。
可以通过 Executors 的 newCachedThreadPool() 方法创建:
newCachedThreadPool() 具体实现:
可以看到 corePoolSize 为 0,maximumPoolSize 为 Integer.MAX_VALUE,证明这种线程池没有核心线程,但是有多个非核心线程。
这种线程池的特点就是,当有任务提交时,如果有空闲线程则复用空闲线程,没有的话就新建线程处理。
空闲线程如果超过 60 秒就会被回收。
具体使用如下:
一种只有一个工作线程的线程池。
可以通过 Executors 的 newSingleThreadExecutor() 方法创建:
newSingleThreadExecutor() 具体实现:
从源码可以看出,这种线程池只有一个核心线程,并且总线程数为 1。
具体使用如下:
一种核心线程数量固定,非核心线程数不固定的线程池。
可以通过 Executors 的 newScheduledThreadPool() 方法创建:
newScheduledThreadPool() 具体实现:
从源码可以看出这种线程池的核心线程是固定的,非核心线程数没有限制,但是非核心线程出现空闲后,10 毫秒就会被回收。
具体使用:
创建线程池有哪几种方式?
一:创建大小不固定的线程池 二:创建固定数量线程的线程池 三:创建单线程的线程池 四:创建定时线程 1.创建大小不固定的线程池[java] view plain copypackage com.peace.pms.Test; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @Author: cxx * @Date: 2018/3/3 17:16 */ public class ThreadPoolDemo { public static class Taskdemo implements Runnable{ @Override public void run() { for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } } } public static void main(String[] args) { ExecutorService es=Executors.newFixedThreadPool(2); for(int i=0;i<10;i++){ Taskdemo tc=new Taskdemo(); es.execute(tc); } es.shutdown(); } } 2.创建固定数量线程的线程池[java] view plain copypublic static void main(String[] args) { ExecutorService es=Executors.newFixedThreadPool(2); for(int i=0;i<10;i++){ Taskdemo tc=new Taskdemo(); es.execute(tc); } es.shutdown(); } 3.创建单线程的线程池[java] view plain copypublic static void main(String[] args) { ExecutorService es=Executors.newSingleThreadExecutor(); for(int i=0;i<10;i++){ Taskdemo tc=new Taskdemo(); es.execute(tc); } es.shutdown(); } 4.创建定时线程[java] view plain copypublic static void main(String[] args) { ScheduledExecutorService es=Executors.newScheduledThreadPool(2); for(int i=0;i<10;i++){ Taskdemo tc=new Taskdemo(); //参数1:目标对象 //参数2:隔多长时间开始执行线程, //参数3:执行周期 //参数4:时间单位 es.scheduleAtFixedRate(tc, 30, 10, TimeUnit.MILLISECONDS); } es.shutdown(); }
线程池的几种常见的创建的方式
一:创建大小不固定的线程池 二:创建固定数量线程的线程池 三:创建单线程的线程池 四:创建定时线程 1.创建大小不固定的线程池[java] view plain copypackage com.peace.pms.Test; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @Author: cxx * @Date: 2018/3/3 17:16 */ public class ThreadPoolDemo { public static class Taskdemo implements Runnable{ @Override public void run() { for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } } } public static void main(String[] args) { ExecutorService es=Executors.newFixedThreadPool(2); for(int i=0;i<10;i++){ Taskdemo tc=new Taskdemo(); es.execute(tc); } es.shutdown(); } } 2.创建固定数量线程的线程池[java] view plain copypublic static void main(String[] args) { ExecutorService es=Executors.newFixedThreadPool(2); for(int i=0;i<10;i++){ Taskdemo tc=new Taskdemo(); es.execute(tc); } es.shutdown(); } 3.创建单线程的线程池[java] view plain copypublic static void main(String[] args) { ExecutorService es=Executors.newSingleThreadExecutor(); for(int i=0;i<10;i++){ Taskdemo tc=new Taskdemo(); es.execute(tc); } es.shutdown(); } 4.创建定时线程[java] view plain copypublic static void main(String[] args) { ScheduledExecutorService es=Executors.newScheduledThreadPool(2); for(int i=0;i<10;i++){ Taskdemo tc=new Taskdemo(); //参数1:目标对象 //参数2:隔多长时间开始执行线程, //参数3:执行周期 //参数4:时间单位 es.scheduleAtFixedRate(tc, 30, 10, TimeUnit.MILLISECONDS); } es.shutdown(); }
使用线程池时一定要注意的五个点
很多场景下应用程序必须能够处理一系列传入请求,简单的处理方式是通过一个线程顺序的处理这些请求,如下图: 单线程策略的优势和劣势都非常明显: 优势:设计和实现简单;劣势:这种方式会带来处理效率的问题,单线程的处理能力是有限,不能发挥多核处理器优势。 在这种场景下我们就需要考虑并发,一个简单的并发策略就是Thread-Per-Message模式,即为每个请求使用一个新的线程。 Thread-Per-Message策略的优势和劣势也非常明显: 优势:设计和实现比较简单,能够同时处理多个请求,提升响应效率; 劣势:主要在两个方面 1.资源消耗 引入了在串行执行中所没有的开销,包括线程创建和调度,任务处理,资源分配和回收以及频繁上下文切换所需的时间和资源。2.安全 有没有一种方式可以并发执行又可以克服Thread-Per-Message的问题? 采用线程池的策略,线程池通过控制并发执行的工作线程的最大数量来解决Thread-Per-Message带来的问题。可见下图,请求来临时先放入线程池的队列 线程池可以接受一个Runnable或Callable任务,并将其存储在临时队列中,当有空闲线程时可以从队列中拿到一个任务并执行。 反例(使用 Thread-Per-Message 策略) 正例(使用 线程池 策略) JAVA 中(JDK 1.5+)线程池的种类: 程序不能使用来自有界线程池的线程来执行依赖于线程池中其他任务的任务。 有两个场景: 要缓解上面两个场景产生的问题有两个简单的办法: 真正解决此类方法还是需要梳理线程池执行业务流程,不要在有界线程池中执行相互依赖的任务,防止出现竞争和死锁。 向线程池提交的任务需要支持中断。从而保证线程可以中断,线程池可以关闭。线程池支持 java.util.concurrent.ExecutorService.shutdownNow() 方法,该方法尝试停止所有正在执行的任务,停止等待任务的处理,并返回等待执行的任务的列表。 但是 shutdownNow() 除了尽力尝试停止处理主动执行的任务之外不能保证一定能够停止。例如,典型的实现是通过Thread.interrupt()来停止,因此任何未能响应中断的任务可能永远不会终止,也就造成线程池无法真正的关闭。 反例: 正例: 线程池中的所有任务必须提供机制,如果它们异常终止,则需要通知应用程序. 如果不这样做不会导致资源泄漏,但由于池中的线程仍然被会重复使用,使故障诊断非常困难或不可能。 在应用程序级别处理异常的最好方法是使用异常处理。异常处理可以执行诊断操作,清理和关闭Java虚拟机,或者只是记录故障的详细信息。 也就是说在线程池里执行的任务也需要能够抛出异常并被捕获处理。 任务恢复或清除操作可以通过重写 java.util.concurrent.ThreadPoolExecutor 类的 afterExecute() 钩子来执行。 当任务通过执行其 run() 方法中的所有语句并且成功结束任务,或者由于异常而导致任务停止时,将调用此钩子。 可以通过自定义 ThreadPoolExecutor 服务来重载 afterExecute()钩子。 还可以通过重载 terminated() 方法来释放线程池获取的资源,就像一个finally块。 反例: 任务意外终止时作为一个运行时异常,无法通知应用程序。此外,它缺乏恢复机制。因此,如果Task抛出一个NullPointerException ,异常将被忽略。 正例: 另外一种方式是使用 ExecutorService.submit() 方法(代替 execute() 方法)将任务提交到线程池并获取 Future 对象。 当通过 ExecutorService.submit() 提交任务时,抛出的异常并未到达未捕获的异常处理机制,因为抛出的异常被认为是返回状态的一部分,因此被包装在ExecutionException ,并由Future.get() 返回。 java.lang.ThreadLocal 类提供线程内的本地变量。根据Java API ThreadLocal对象需要关注那些对象被线程池中的多个线程执行的类。 线程池缓存技术允许线程重用以减少线程创建开销,或者当创建无限数量的线程时可以降低系统的可靠性。 当 ThreadLocal 对象在一个线程中被修改,随后变得可重用时,在重用的线程上执行的下一个任务将能看到该线程上执行过的上一个任务修改的ThreadLocal 对象的状态。 所以要在使用线程池时重新初始化的ThreadLocal对象实例。 反例: DiaryPool类创建了一个线程池,它可以通过一个共享的无界的队列来重用固定数量的线程。 在任何时候,不超过numOfThreads个线程正在处理任务。如果在所有线程都处于活动状态时提交其他任务,则 它们在队列中等待,直到线程可用。 当线程循环时,线程的线程局部状态仍然存在。 下表显示了可能的执行顺序: 时间任务线程池提交方法日期1t11doSomething1()星期五2t22doSomething2()星期一3t31doSomething3()星期五 在这个执行顺序中,期望从doSomething2() 开始的两个任务( t 2和t 3 doSomething2() 将当天视为星 期一。然而,因为池线程1被重用,所以t 3观察到星期五。 解决方案(try-finally条款) 符合规则的方案removeDay() 方法添加到Diary类,并在try‐finally 块中的实现doSomething1() 类的doSomething1() 方法的语句。finally 块通过删除当前线程中的值来恢复threadlocal类型的days对象的初始状态。 如果threadlocal变量再次被同一个线程读取,它将使用initialValue()方法重新初始化 ,除非任务已经明确设置了变量的值。这个解决方案将维护的责任转移到客户端( DiaryPool ),但是当Diary类不能被修改时是一个好的选择。 解决方案(beforeExecute()) 使用一个自定义ThreadPoolExecutor 来扩展 ThreadPoolExecutor并覆盖beforeExecute() 方法。beforeExecute() 方法在Runnable 任务在指定线程中执行之前被调用。该方法在线程 “t” 执行任务 “r” 之前重新初始化 threadlocal 变量。