1.线程池的作用
池化技术应用:线程池、数据库连接池、http连接池等等。
池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。
线程池提供了一种限制、管理资源的策略。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。
使用线程池的好处:
-
降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
-
提高响应速度:当任务到达时,可以不需要等待线程创建就能立即执行。
-
提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,监控和调优。
2.Executor框架
Executor框架不仅包括了线程池的管理,还提供了线程工厂、队列以及拒绝策略等,让并发编程变得更加简单。
Executor框架的使用示意图
-
主线程首先要创建实现Runnable或Callable接口的任务对象。
-
把创建完成的实现Runnable/Callable接口的对象直接交给ExecutorService执行:
-
ExecutorService.execute(Runnable command)
-
ExecutorService.sumbit(Runnable command)
-
ExecutorService.sumbit(Callable <T> task)
-
-
如果执行ExecutorService.submit(...),ExecutorService将返回一个实现Future接口的对象。 最后,主线程可以执行FutureTask.get()方法来等待任务执行完成。主线程也可以执行FutureTask.cancel()来取消次任务的执行。
线程池分析原理
3.线程池的使用
java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。下面我们来看一下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); ... }
从上面的代码可以得知,ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。
线程池构造方法参数:
corePoolSize:线程池中核心线程的数量,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到任务队列中
maximumPoolSize:线程池中最大线程数量,队列填满之后在核心线程数的基础上,额外增加的线程数的上限,如果是*队列,因为*队列不会被填满所以此参数不会起作用
keepAliveTime:非核心线程的超时时长,当系统中非核心线程闲置时间超过keepAliveTime之后,则会被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,则该参数也表示核心线程的超时时长
unit:keepAliveTime 参数的单位,有纳秒、微秒、毫秒、秒、分、时、天等,在TimeUnit类中有对应7种静态属性
workQueue:线程池中的任务队列,该队列主要用来存储已经被提交但是尚未执行的任务。存储在这里的任务是由ThreadPoolExecutor的execute方法提交来的。
1.ArrayBlockingQueue 有界队列,可以设置容量,容量满了之后如果线程数没超过maximumPoolSize会创建新的线程,(先进先出)的方式来进行存取 2.LinkedBlockingQueue *队列,这个表示一个大小不确定的BlockingQueue,在LinkedBlockingQueue的构造方法中可以传一个int类型的数据,这样创建出来的LinkedBlockingQueue是有大小的,也可以不传,不传的话,LinkedBlockingQueue的大小就为Integer.MAX_VALUE,处理的速度跟不上任务提交的速度可能会内存不足导致OOM 3.SynchronousQueue 直接交接,不能存储任务
threadFactory:为线程池提供创建新线程的功能,这个我们一般使用默认即可
handler:拒绝策略,当线程无法执行新任务时(一般是由于线程池中的线程数量已经达到最大数或者线程池关闭导致的),默认情况下,当线程池无法处理新线程时,会抛出一个RejectedExecutionException。
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
1.手动创建线程池
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
2.自动创建,由JDK提供已经设置固定参数的线程池
(1)FixedThreadPool: Executors.newFixedThreadPool();
FixedThreadPool是一个核心线程数量固定的线程池,核心线程数和最大线程数一样,说明在FixedThreadPool中没有非核心线程,所有的线程都是核心线程,且线程的超时时间为0,说明核心线程即使在没有任务可执行的时候也不会被销毁(这样可让FixedThreadPool更快速的响应请求),最后的线程队列是一个LinkedBlockingQueue,但是LinkedBlockingQueue却没有参数,这说明线程队列的大小为Integer.MAX_VALUE(2的31次方减1),看完参数,我们大概也就知道了FixedThreadPool的工作特点了,当所有的核心线程都在执行任务的时候,新的任务只能进入线程队列中进行等待,直到有线程被空闲出来
(2)SingleThreadPool: Executors.newSingleThreadExecutor();
singleThreadExecutor和FixedThreadPool很像,不同的就是SingleThreadExecutor的核心线程数只有1,使用SingleThreadExecutor的一个最大好处就是可以避免我们去处理线程同步问题,其实如果我们把FixedThreadPool的参数传个1,效果就和SingleThreadExecutor一致了
(3)CachedThreadPool: Executors.newCachedThreadPool();
CachedThreadPool中是没有核心线程的,但是它的最大线程数却为Integer.MAX_VALUE,另外,它是有线程超时机制的,超时时间为60秒,这里它使用了SynchronousQueue作为线程队列,SynchronousQueue的特点上文已经说过了,这里不再赘述。那么我们提交到CachedThreadPool消息队列中的任务在执行的过程中有什么特点呢?由于最大线程数为无限大,所以每当我们添加一个新任务进来的时候,如果线程池中有空闲的线程,则由该空闲的线程执行新任务,如果没有空闲线程,则创建新线程来执行任务。根据CachedThreadPool的特点,我们可以在有大量任务请求的时候使用CachedThreadPool,因为当CachedThreadPool中没有新任务的时候,它里边所有的线程都会因为超时而被终止
(4)ScheduledThreadPool: Executors.newScheduledThreadPool();
ScheduledThreadPool是一个具有定时定期执行任务功能的线程池。它的核心线程数量是固定的(我们在构造的时候传入的),但是非核心线程是无穷大,当非核心线程闲置时,则会被立即回收
private final ReentrantLock lock = new ReentrantLock();
lock.lock(); 加锁
lock.unlock(); 释放锁
1.shutDown() 关闭线程池,不影响已经提交的任务
2.shutDownNow() 关闭线程池,并尝试去终止正在执行的线程
3.allowCoreThreadTimeOut(boolean value) 允许核心线程闲置超时时被回收
4.submit 一般情况下我们使用execute来提交任务,但是有时候可能也会用到submit,使用submit的好处是submit有返回值
使用submit时我们可以通过实现Callable接口来实现异步任务。在call方法中执行异步任务,返回值即为该任务的返回值。Future是返回结果,返回它的isDone属性表示异步任务执行成功!