Java开发之高并发必备篇(七)——线程池

线程池的使用

我们之前使用线程的时候都是自己手动的通过new Thread等方式创建的,使用完了销毁线程,在开发中这样的操作会造成一些弊端:

◆ 对线程频繁的创建和销毁会消耗系统资源
◆ 当有空闲线程的时候无法复用它,需要创建新的线程这样响应速慢,效率低下
◆ 并发线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或OOM(OutOfMemoryError内存溢出)
◆ 缺乏更多功能支持,如延时执行、定期执行等

怎么解决上面的一些问题呢?没错就是使用池化技术了。

· 线程池介绍

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池可以让我们重用已经存在的线程,降低线程创建和销毁的消耗,提高系统的响应速度,并且线程池可以对线程进行管控,不同类型的线程池还会提供不同的功能,例如:有的可以提供延时或者定时执行任务。

· 线程池核心参数

corePoolSize:核心池的大小,这个参数跟下面讲述的线程池的实现原理有很大的关系。默认情况下,在创建了线程池后,线程池中的线程数为0(除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,表示在没有任务的时候预先创建好corePoolSize个线程或者一个线程),当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列workQueue当中;

workQueue:一个阻塞任务队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,它有三个队列选择:

◆ ArrayBlockintQueue:有数量限制的队列

◆ LinkedBlockingQueue:没有数量限制的队列

◆ SynchronousQueue:队列不用于存储数据,数量为0,用于数据的移交,队列中插入和删除操作都是同步执行的。

maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;

keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。

handler: 表示当拒绝处理任务时的策略,有以下四种取值:

◆ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

◆ ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。

◆ ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)。

◆ ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。

· 线程池原理图解

Java开发之高并发必备篇(七)——线程池_第1张图片

· Java对线程池的支持

在JDK中有个Executors类提供了四个静态的方法:newCachedThreadPool、newFixedThreadPool、newScheduledThreadPool、newSingleThreadExecutor可以让我们获取四种不同类型的线程池。这个四个方法都返回一个ExecutorService及其子接口的实现类,这些接口中定义了线程池操作的一些基本操作。下面我们来认识下这四个线程池:

1.newCachedThreadPool

创建一个数量无限制的可缓存的线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。比较适合一些执行任务时间端的线程使用场景。

Java开发之高并发必备篇(七)——线程池_第2张图片

2.newFixedThreadPool

创建一个固定大小的线程池,可控制线程最大并发数,超出的线程会在队列中等待。定长线程池的大小最好根据系统资源需要进行设置。例如:对于IO密集型的任务一般设置为Runtime.getRuntime().availableProcessors()*2即可利用进程数量的2倍等;

它比较适合一些执行任务时间长消耗资源大的情况,这样把线程数量控制好不会给系统造成太大的负担。

Java开发之高并发必备篇(七)——线程池_第3张图片

3.newScheduledThreadPool

创建一个指定大小的线程池,支持延时和周期性任务执行。所以它的应用场景不言而喻了,例如:定时备份、定时检测等;

在这里插入图片描述

在这里插入图片描述

4.newSingleThreadExecutor

创建一个有顺序的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行(有FIFO先进先出, LIFO后进先出, 优先级)。

Java开发之高并发必备篇(七)——线程池_第4张图片

· 线程池常用操作方法

◆ submit(): 表示提交执行一个线程任务,支持Runnable和Callable两种方式,并返回一个代表任务待处理结果的Future对象。

◆ execute():提交执行一个Runnable任务,获取不到任务执行的结果。

◆ shutdown():启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。如果已经关闭,调用没有额外的作用。

◆ shutdownNow():尝试停止所有主动执行的任务,停止等待任务的处理,并返回正在等待执行的任务列表。此方法不等待主动执行的任务终止。

◆ isShutdown():判断线程池是否已经停止。

· 线程池的使用

1.newCachedThreadPool

Java开发之高并发必备篇(七)——线程池_第5张图片

运行结果:

Java开发之高并发必备篇(七)——线程池_第6张图片

2.newFixedThreadPool

Java开发之高并发必备篇(七)——线程池_第7张图片

运行结果:

Java开发之高并发必备篇(七)——线程池_第8张图片

3.newSingleThreadExecutor

Java开发之高并发必备篇(七)——线程池_第9张图片

运行结果:

Java开发之高并发必备篇(七)——线程池_第10张图片

4.newScheduledThreadPool

Java开发之高并发必备篇(七)——线程池_第11张图片

运行结果:

Java开发之高并发必备篇(七)——线程池_第12张图片

当我们调用shutdown和shutdownNow的时候查看下任务执行情况:

executorService.shutdown()

Java开发之高并发必备篇(七)——线程池_第13张图片

我们发现shutdown是等待任务执行完之后才结束线程池。

executorService.shutdownNow();

Java开发之高并发必备篇(七)——线程池_第14张图片

我们发现延迟任务都没有执行到就结束了线程池,所以shutdownNow是不管任务是否执行完都会结束线程池。

总结:在真实开发中我们需要按照我们的业务需要选择合适的线程池,如果要执行时间短但是任务比较多那么就选择newCachedThreadPool数量不限制的可缓存线程池,如果我们执行任务时间长消耗资源那么我们需要选择newFixedThreadPool这种固定长度的线程池合理控制资源消耗,如果我们执行的任务需要按照顺序执行那么我们需要使用newSingleThreadExecutor,如果我们有延时执行的任务或者周期性执行的任务那么我们需要选择newScheduledThreadPool。

你可能感兴趣的:(互联网,软件工程师,程序员,java,程序员,python,软件测试,开发语言)