Java中常见的线程池
为什么使用线程池
-
重用线程池的线程,避免因为线程的创建和销毁所带来的性能开销。
-
有效控制线程池的最大并发数,避免大量的线程之间因抢占系统资源而阻塞。
-
能够对线程进行简单的管理,并提供一下特定的操作如:可以提供定时、定期、单线 程、并发数控制等功能。
线程池可能带来的风险
-
死锁
任何多线程应用程序都有死锁风险。当一组进程或线程中的每一个都在等待一个只有该组 中另一个进程才能引起的事件时,我们就说这组进程或线程死锁了。
-
资源不足
一般是由于线程池设置过大引起,需要合理设置线程池大小。
-
并发错误
线程池和其它排队机制依靠使用 wait() 和 notify() 方法,这两个方法都难于使用。如 果编码不正确,那么可能丢失通知,导致线程保持空闲状态,尽管队列中有工作要处理。
-
线程泄露
任务抛出一个 RuntimeException 或一个 Error 时。如果池类没有捕捉到它们,那么线 程只会退出而线程池的大小将会永久减少一个。当这种情况发生的次数足够多时,线程池 最终就为空,而且系统将停止,因为没有可用的线程来处理任务。
-
请求过载
线程池任务队列中,待执行的任务堆积过多,消耗太多的系统资源并引起资源缺乏。
Executor
在Java 5之后,并发编程引入了一堆新的启动、调度和管理线程的API。 Executor 框架 便是Java 5中引入的,其内部使用了线程池机制,它在 java.util.cocurrent 包下,通过 该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。因此,在Java 5之 后,通过 Executor 来启动线程比使用 Thread 的 start 方法更好,除了更易管理,效率更 好(用线程池实现,节约开销)外,还有关键的一点:有助于避免this逃逸问题——如果 我们在构造器中启动一个线程,因为另一个任务可能会在构造器结束之前开始执行,此时 可能会访问到初始化了一半的对象用Executor在构造器中。 Eexecutor 作为灵活且强大的 异步执行框架,其支持多种不同类型的任务执行策略,提供了一种标准的方法将任务的提 交过程和执行过程解耦开发,基于生产者-消费者模式,其提交任务的线程相当于生产者, 执行任务的线程相当于消费者,并用Runnable来表示任务,Executor的实现还提供了对生 命周期的支持,以及统计信息收集,应用程序管理机制和性能监视等机制。
Executor框架包括:线程池,Executor,Executors,ExecutorService,
CompletionService,Future,Callable等。
Executor和ExecutorService
-
Executor:一个接口,其定义了一个接收 Runnable 对象的方法 executor ,其方法签名 为 executor(Runnable command) ,该方法接收一个 Runable 实例,它用来执行一个任务, 任务即一个实现了 Runnable 接口的类,一般来说, Runnable 任务开辟在新线程中的使用 方法为: new Thread(new RunnableTask()).start() ,但在 Executor 中,可以使用
Executor而不用显示地创建线程: executor.execute(new RunnableTask()) ; Executor 接 口并不严格要求执行是异步的。
-
ExecutorService: 是一个比 Executor 使用更广泛的子类接口,其提供了生命周期管理的方 法,返回 Future 对象,以及可跟踪一个或多个异步任务执行状况返回 Future 的方法;可 以调用 ExecutorService 的 shutdown() 方法来平滑地关闭 ExecutorService ,调用该方 法后,将导致 ExecutorService 停止接受任何新的任务且等待已经提交的任务执行完成
(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有 已经提交的任务执行完毕后将会关闭 ExecutorService 。因此我们一般用该接口来实现和 管理多线程。
Executors
Executors 类,是一个工具、工厂类。提供了一系列工厂方法用于创建线程池,返回的线 程池都实现了 ExecutorService 接口。常见的线程池都可以通过此类创建。
CachedThreadPool
可缓存线程池。一种线程数量不定的线程池,并且其最大线程数为 Integer.MAX_VALUE , 这个数是很大的,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则 新建线程。 这些池通常会提高执行许多短暂异步任务的程序的性能。 调用execute将重用 以前构造的线程(如果可用)。 如果没有可用的线程,将创建一个新的线程并将其添 加到该池中。但是线程池中的空闲线程都有超时限制,这个超时时长是60秒,超过60秒 闲置线程将被终止并从缓存中删除。 因此,长时间保持闲置的池将不会消耗任何资源。
创建方式:
public static ExecutorService newCachedThreadPool(); public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory);
newCachedThreadPool 使用:
ExecutorService pool = Executors.newCachedThreadPool(); System.out.println("当前时间: " + LocalTime.now()); for (int i = 0; i < 10; i++) { int number = i; pool.execute(new Runnable() { public void run() { System.out.println("当前时间:" + LocalTime.now() + ", 线程: " + Thread.currentThread().getName() + ", 序号:" + number); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); }
FixedThreadPool
创建一个可重用固定个数的线程池, 当线程处于空闲状态时,它们并不会被回收,除非 线程池被关闭了,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池 队列(没有大小限制)中。由于 newFixedThreadPool 只有核心线程并且这些核心线程不 会被回收,这样它更加快速的响应外界的请求。
创建方式:
public static ExecutorService newFixedThreadPool(int nThreads); public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory);
ExecutorService pool = Executors.newFixedThreadPool(2); System.out.println("当前时间: " + LocalTime.now()); for (int i = 0; i < 10; i++) { int number = i; pool.execute(new Runnable() { public void run() { System.out.println("当前时间:" + LocalTime.now() + ", 线程: " + Thread.currentThread().getName() + ", 序号:" + number); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); }
ScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。它的线程数量是固定的,它可安排 给定延迟后运行命令或者定期地执行,这类线程池主要用于执行定时任务和具有固定周期 的重复任务。
创建方式:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize); public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)
使用: 定时执行(延迟)
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2); System.out.println("当前时间: " + LocalTime.now()); for (int i = 0; i < 3; i++) { int number = i; pool.schedule(new Runnable() { public void run() { System.out.println("当前时间:" + LocalTime.now() + ", 线程: " + Thread.currentThread().getName() + ", 序号:" + number); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }, 5, TimeUnit.SECONDS); }
周期执行
/** command - 要执行的任务 initialDelay - 延迟第一次执行的时间 period - 连续执行之间的间隔 unit - initialDelay和period参数的时间单位 */ public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit); public static void main(String[] args) { ScheduledExecutorService pool = Executors.newScheduledThreadPool(2); System.out.println("当前时间: " + LocalTime.now()); // 两秒后开始执行任务,每隔 5 秒执行一次 pool.scheduleAtFixedRate(new Runnable() { public void run() { System.out.println("当前时间:" + LocalTime.now() + ", 线程: " + Thread.currentThread().getName()); } }, 2, 5, TimeUnit.SECONDS); } /** command - 要执行的任务 initialDelay - 延迟第一次执行的时间 delay - 一个执行终止与下一个执行的开始之间的延迟 unit - initialDelay和delay参数的时间单位 */ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit); public static void main(String[] args) { ScheduledExecutorService pool = Executors.newScheduledThreadPool(2); System.out.println("当前时间: " + LocalTime.now()); // 两秒后开始执行任务,每隔 5 秒执行一次 pool.scheduleWithFixedDelay(new Runnable() { public void run() { System.out.println("当前时间:" + LocalTime.now() + ", 线程: " + Thread.currentThread().getName()); } }, 2, 5, TimeUnit.SECONDS); }
-
区别
scheduleAtFixedRate 方法,如果要执行的任务耗时比任务执行的间隔时间长,下次 任务执行的时间是上一次任务执行后就执行。(排队)
scheduleWithFixedDelay 方法,下次任务的执行时间是在本次任务执行所需时间再加 上间隔时间。
SingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指 定顺序(FIFO, LIFO, 优先级)执行。
public static ExecutorService newSingleThreadExecutor(); public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor(); System.out.println("当前时间: " + LocalTime.now()); for (int i = 0; i < 7; i++) { int number = i; pool.execute(new Runnable() { public void run() { System.out.println("当前时间:" + LocalTime.now() + ", 线程: " + Thread.currentThread().getName() + ", 序号:" + number); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); }
Executor VS ExecutorService VS Executors
正如上面所说,这三者均是 Executor 框架中的一部分。Java 开发者很有必要学习和理解 他们,以便更高效的使用 Java 提供的不同类型的线程池。总结一下这三者间的区别,以便 大家更好的理解:
-
Executor 和 ExecutorService 这两个接口主要的区别是: ExecutorService 接口继 承了 Executor 接口,是 Executor 的子接口
-
Executor 接口定义了 execute() 方法用来接收一个 Runnable 接口的对象,而 Execu torService 接口中的 submit() 方法可以接受 Runnable 和 Callable 接口的对象。
-
Executor 中的 execute() 方法不返回任何结果,而 ExecutorService 中的 submit () 方法可以通过一个 Future 对象返回运算结果。 除了允许客户端提交一个任务, ExecutorService 还提供用来控制线程池的方法。比 如:调用 shutDown() 方法终止线程池。
-
Executors 类提供工厂方法用来创建不同类型的线程池。比如: newSingleThreadExec utor() 创建一个只有一个线程的线程池, newFixedThreadPool(int numOfThreads) 来 创建固定线程数的线程池, newCachedThreadPool() 可以根据需要创建新的线程,但如 果已有线程是空闲的会重用已有线程。
阿里开发规范中强制要求禁用Executors 来创建线程池 【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方 式,这样 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors 返回的线程池对象的弊端如下: 1) FixedThreadPool 和 SingleThreadPool : 允许的请求队列长度为 Integer.MAX_VALUE ,可能会堆积大量的请求,从而导致 OOM。 2) CachedThreadPool 和 ScheduledThreadPool : 允许的创建线程数量为 Integer.MAX_VALUE ,可能会创建大量的线程,从而导致 OOM。 分析
ThreadPoolExecutor
使用 ThreadPoolExecutor 可以创建出符合自己的业务场景需要的线程池,
public class ThreadPoolExecutor extends AbstractExecutorService
构造方法
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory, RejectedExecutionHandler handler)
实际上最后都调用的是最后一个(参数最多的那个)构造方法,所以我们以最后一个为例 讲解参数:
-
corePoolSize: 线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程 执行任务,直到当前线程数等于 corePoolSize ;如果当前线程数为 corePoolSize , 继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的 prestartAll CoreThreads() 方法,线程池会提前创建并启动所有核心线程。当线程数小于等于 core PoolSize 时,默认情况下线程会一直存活在线程池中,即使线程处于空闲状态。如果
-
allowCoreThreadTimeOut 被设置为 true 时,无论线程数多少,那么线程处于空闲状 态超过一定时间就会被销毁掉。
-
maximumPoolSize: 线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提 交任务,则创建新的线程执行任务,前提是当前线程数小于 maximumPoolSize ;
-
keepAliveTime: 线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时 间;默认情况下,该参数只在线程数大于 corePoolSize 时才有用;如果 allowCoreThr eadTimeOut 被设置为 true 时,无论线程数多少,线程处于空闲状态超过一定时间就会 被销毁掉。
-
unit: keepAliveTime 的单位。 TimeUnit 是一个枚举类型,其包括:
-
NANOSECONDS :1微毫秒 = 1微秒 / 1000 MICROSECONDS :1微秒 = 1毫秒 / 1000 MILLISECONDS :1毫秒 = 1秒 /1000
-
SECONDS :秒
-
MINUTES :分
-
HOURS :小时
-
DAYS :天
-
-
workQueue: 用来保存等待被执行的任务的阻塞队列,且任务必须实现 Runable 接口, 有如下阻塞队列:
-
ArrayBlockingQueue :基于数组结构的有界阻塞队列,按FIFO排序任务;
-
LinkedBlockingQuene :基于链表结构的无界阻塞队列,按FIFO排序任务,吞吐量 通常要高于 ArrayBlockingQuene ;
-
SynchronousQuene :一个不存储元素的阻塞队列,每个插入操作必须等到另一个 线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于 LinkedBl ockingQuene ;
-
-
threadFactory: 创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置 一个具有识别度的线程名
-
handler: 线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提 交任务,必须采取一种策略处理该任务,线程池提供了4种策略:
-
AbortPolicy :直接抛出异常,默认策略;
-
CallerRunsPolicy :用调用者所在的线程来执行任务;
-
DiscardOldestPolicy :丢弃阻塞队列中靠最前的任务,并执行当前任务;
-
DiscardPolicy :直接丢弃任务;
-
-
ScheduledThreadPool 线程池是使用 ScheduledThreadPoolExecutor 类创建的。
-
ScheduledThreadPoolExecutor 类是 ThreadPoolExecutor 的子类
缓存池线程
import java.time.LocalDateTime; import java.util.concurrent.*; public class CachedThread { public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L, TimeUnit.SECONDS, new SynchronousQueue<>()); for (int i = 0; i < 5; i++) { int sum = i; poolExecutor.execute(()->{ System.out.println("当前时间"+ LocalDateTime.now()+"线程"+Thread.currentThread().getName()+"序号"+sum); }); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }
核心线程
import java.time.LocalDateTime; import java.util.concurrent.*; public class ThreadExercise4 { public static void main(String[] args) { // ExecutorService executorService = Executors.newFixedThreadPool(); ThreadPoolExecutor executor = new ThreadPoolExecutor(4,Integer.MAX_VALUE,0l, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>()); for (int i = 0; i < 5; i++) { int sum = i; executor.execute(()->{ System.out.println("当前时间"+ LocalDateTime.now()+"线程"+Thread.currentThread().getName()+"序号"+sum); }); try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }
定长线程
import java.time.LocalDateTime; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ThreadExercise5 { public static void main(String[] args) { // ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(); ScheduledThreadPoolExecutor scheduled = new ScheduledThreadPoolExecutor(3); for (int i = 0; i < 5; i++) { int sum = i; scheduled.schedule(()->{ System.out.println("当前时间"+ LocalDateTime.now()+"线程"+Thread.currentThread().getName()+"序号"+sum); },2, TimeUnit.SECONDS); } } }
单线程
import java.time.LocalDateTime; import java.util.concurrent.*; public class ThreadExercise6 { public static void main(String[] args) { ExecutorService executorService = Executors.newSingleThreadExecutor(); ThreadPoolExecutor executor = new ThreadPoolExecutor(1,1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>()); for (int i = 0; i < 5; i++) { int sum = i; executor.execute(()->{ System.out.println("当前时间"+ LocalDateTime.now()+"线程"+Thread.currentThread().getName()+"序号"+sum); }); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }