1传统创建线程的方式带来的弊端?
1.创建和销毁线程需要时间,假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。如果:T1 + T3 远大于 T2,那么会得不偿失。
2.其次线程也需要占用内存空间,大量的线程会抢占宝贵的内存资源,可能会导致out of memory异常。
3.且大量的线程回收也会给GC带来很大的压力,延长GC的停顿时间。
4.最后,大量的线程也会抢占cpu的资源,cpu不停的在各个线程上下文切换中,反而没有时间去处理线程运行的时候该处理的任务。
2.什么是线程池?
线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程,不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。
在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁,这就是”池化资源”技术产生的原因。因此为了避免频繁的创建和销毁线程,让创建的线程进行复用,就有了线程池的概念。线程池里会维护一部分活跃线程,如果有需要,就去线程池里取线程使用,用完即归还到线程池里,免去了创建和销毁线程的开销,且线程池也会对线程的数量有一定的限制。
3.线程池的优势?
线程池有如下的优势:
(1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
(2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
(3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
4.线程池的类的继承关系?
-
Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable command),execute()方法;返回值为void,参数为Runnable类型,用来执行传进去的任务。
-
ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等;
-
抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法。
-
ThreadPoolExecutor继承了类AbstractExecutorService。
5.ThreadPoolExecutor源码刨析
1.构造函数
public ThreadPoolExecutor(int corePoolSize,//核心线程数的大小
int maximumPoolSize,//程池中允许的最大线程数
long keepAliveTime,//空闲线程允许的最大的存活时间
TimeUnit unit,//存活时间的单位
BlockingQueue workQueue)//阻塞任务队列//使用默认线程工厂和默认拒绝策略
{this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler); }public ThreadPoolExecutor(int corePoolSize,//核心线程数的大小
int maximumPoolSize,//程池中允许的最大线程数
long keepAliveTime,//空闲线程允许的最大的存活时间
TimeUnit unit,//存活时间的单位
BlockingQueue workQueue,//阻塞任务队列
ThreadFactory threadFactory) //线程工厂用来创建线程//使用自定义线程工厂和默认拒绝策略
{ this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);}public ThreadPoolExecutor(int corePoolSize,//核心线程数的大小
int maximumPoolSize,//程池中允许的最大线程数
long keepAliveTime,//空闲线程允许的最大的存活时间
TimeUnit unit,//存活时间的单位
BlockingQueue workQueue,//阻塞任务队列
RejectedExecutionHandler handler) //拒绝策略//使用默认线程工厂和自定义拒绝策略
{this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);}public ThreadPoolExecutor(int corePoolSize,//核心线程数的大小
int maximumPoolSize,//程池中允许的最大线程数
long keepAliveTime,//空闲线程允许的最大的存活时间
TimeUnit unit,//存活时间的单位
BlockingQueue workQueue,//阻塞任务队列
ThreadFactory threadFactory,//线程工厂用来创建线程
RejectedExecutionHandler handler) //拒绝策略//使用自定义线程工厂和自定义的拒绝策略
{ if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
通过源码可以观察到,一共有六个参数;六个参数表示的意思分别如下;
四个构造函数对于前四个参数在使用ThreadPoolExecutor时,都要设定
1.只设定前四个参数
2.设定前四个参数和自定义线程工厂
3.设定前四个参数和自定义拒绝策略
4.设定前四个参数和自定义线程工厂以及拒绝策略
• corePoolSize: 核心线程数的大小
• maximumPoolSize: 线程池中允许的最大线程数
• keepAliveTime: 空闲线程允许的最大的存活时间
• unit: 存活时间的单位
• workQueue: 阻塞任务队列
• threadFactory: 线程工厂用来创建线程
• handler: 拒绝策略,针对当队列满了时新来任务的处理方式 -
execute(Runnable)
首先来介绍一下execute()方法;这个方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
在对execute()方法进行介绍时,先介绍ThreadPoolExecute类中的属性
//ctl包含线程池的运行状态(高3位)和有效线程数信息(低29位)
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//有效线程数所占位数(29)
private static final int COUNT_BITS = Integer.SIZE - 3;
//理论上有效线程数的最大值
private static final int CAPACITY = (1 << COUNT_BITS) - 1;/*线程池运行状态/
//线程池能够处理新任务,并且处理队列任务
private static final int RUNNING = -1 << COUNT_BITS;
//线程池不接受新任务,但是会处理队列任务
private static final int SHUTDOWN = 0 << COUNT_BITS;
//线程池不接受新任务,也不处理队列任务,会中断进行中的任务
private static final int STOP = 1 << COUNT_BITS;
//线程池中所有任务结束,有效线程数为0
private static final int TIDYING = 2 << COUNT_BITS;
//线程池完成状态
private static final int TERMINATED = 3 << COUNT_BITS;//从ctl中解析出线程池运行状态的方法
private static int runStateOf(int c) { return c & ~CAPACITY; }
//从ctl中解析出有效线程数的方法
private static int workerCountOf(int c) { return c & CAPACITY; }
//ctl的初始化方法
private static int ctlOf(int rs, int wc) { return rs | wc; }
在ThreadPoolExecutor中关于execute()方法:
public void execute(Runnable command) {//返回值为void
if (command == null)//如果提交的任务为空,那么就抛出异常
throw new NullPointerException();int c = ctl.get();//获取线程池的运行状态和有效线程数
// 如果有效线程比核心线程数少,则创建新线程(添加核心线程数)
if (workerCountOf© < corePoolSize) {
if (addWorker(command, true))//addworker(command)表示创建一个工作线程并且执行提交的任务
return;
c = ctl.get();
}//如果有效线程数不小于核心线程数,检查线程池是否是RUNNING状态;
//是,将任务对象添加到任务队列
if (isRunning© && workQueue.offer(command)) {
int recheck = ctl.get();//再次获取线程池的运行状态和有效线程数
//当前线程池不处于RUNNING状态,移除任务队列workQueue中的任务对象,并执行拒绝策略
if (! isRunning(recheck) && remove(command))
reject(command);
//当前线程池中的worker数为0,则直接创建一个(非核心)线程,
//task为空的线程在执行时,会直接到任务队列中去获取任务
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}//将任务对象添加到任务队列中失败,则添加到线程池的有效线程中,如果失败,执行拒绝策略
else if (!addWorker(command, false))
reject(command);
}
• defaultHandler:默认被拒绝的执行策略 --> 即在线程池被占满,不再开启新的任务时调用的方法(联系上图:假如核心线程有4个,最大线程数目为5,阻塞队列为10。当有两个任务时,直接交给核心线程去执行,当有7个任务时,核心线程有4个,剩下的三个线程进入阻塞队列。当任务增加到14个时,核心线程已满,任务队列也满了。此时由于最大线程数为5,因此,还可以再创建一个线程。当核心线程,队列,最大线程都满了,即此时已经存在了15个任务。那么再有任务被提交,就会触发执行拒绝策略。 )
3.拒绝任务策略
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略: -
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
-
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
-
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
-
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
6.线程池常见方法介绍:
• 上述介绍的execute()方法;是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
• submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果。
在AbstractExecutorService关于submit方法
/**- @throws RejectedExecutionException {@inheritDoc}
- @throws NullPointerException {@inheritDoc}
/
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
/* - @throws RejectedExecutionException {@inheritDoc}
- @throws NullPointerException {@inheritDoc}
/
public Future submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
/* - @throws RejectedExecutionException {@inheritDoc}
- @throws NullPointerException {@inheritDoc}
*/
public Future submit(Callable task) {
if (task == null) throw new NullPointerException();
RunnableFuture ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
• void shutdown(); 当线程池调用该方法时,线程池的状态则立刻变成SHUTDOWN状态。此时,则不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。
• List shutdownNow(); 执行该方法,线程池的状态立刻变成STOP状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,当然,它会返回那些未执行的任务。
• boolean isShutdown(); 判断线程池是否已关闭
• boolean isTerminated(); 当调用了showdown()方法后,所有任务是否已执行结束.注意:如果不事先调用showdown()方法,则此方法永远返回false.
• boolean awaitTermination(long timeout, TimeUnit unit); 当调用shutdown()方法后,调用此方法可以设置等待时间,等待执行中的任务全部结束,全部结束返回true.如果超时,或线程中断导致未全部结束则返回false.
• Future submit(Callable task); 提交有返回值的Runnable任务.
• List invokeAll(Collection<? extends Callable> tasks,long timeout, TimeUnit unit) 执行传入的任务,当所有任务执行结束或超时后,返回持有任务状态和结果的Future集合.注意:一个任务结束有两种情况:1.正常执行完成;2.抛出异常.
• T invokeAny(Collection<? extends Callable> tasks); 执行传入的任务,只要有任何一个任务成功执行完成(不抛出异常),一旦有结果返回,或抛出异常,则其他任务取消.
7.常用的几种线程池
1.newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
创建一个单线程的线程池,以无界队列( LinkedBlockingQueue)方式运行。这个线程池只有一个线程在工作(如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。)此线程池能够保证所有任务的执行顺序按照任务的提交顺序执行,同一时段只有一个任务在运行。
此类型线程池特别适合于需要保证执行顺序的场合。
2.newFixedThreadPool(int Threads)
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
定义一个定长线程池,可控制最大线程并发数,超过的线程会在队列中等待
创建固定大小的线程池,以无界队列方式运行(LinkedBlockingQueue)。线程池满且线程都为活动状态的时候如果有新任务提交进来,它们会等待直到有线程可用。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。显式调用shutdown将关闭线程池。此类型线程池比较符合常用场合。
核心线程数是传入且固定的,所以称为有界线程池,一般在后台执行一些辅助性的任务,最大线程数与核心线程数相等;假设核心线程数为3,一次执行20个任务:先启动3个线程,剩下17个任务会进入BlockingQueue排队;因为核心线程数数=最大线程数,所以keepAliveTime这个参数是没有意义
3.newCacheThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
必要的时候创建新线程来处理请求,也会重用线程池中已经处于可用状态的线程。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程;当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
阻塞队列是synchronizeQueue
4.ScheduledThreadPoolExecutor
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory, handler);
}
阻塞队列是DelayedWorkQueue;调用父类构造函数ThreadPoolExecutor;核心线程数目为传入的核心线程数;最大线程数取决于设备,不限值;
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
创建一个线程池,该线程池提供延时执行任务或者是周期性执行任务的功能。
根据指定的corePoolSize的大小来创建线程池,maxPoolSize的大小为Integer.MAX_VALUE,线程没有存活时间的限制
参考博文链接:
https://mp.weixin.qq.com/s?src=11×tamp=1599631185&ver=2573&signature=RQKX1lzPzx0vChSv3YJF63OdT0rzL2mqXoBIU9Y5KXRPq5NVazqSZRHEgeHKU2FUci5x1ZFWRBy4QdqWPAcaYpsL2sS5iXmndmjY2Wvn2NMdTLxnViWlAvtlQDHGMws1&new=1