零、线程池的工作原理
1.当有任务来的时候,如果没到达core核心线程数,则创建线程
2.当有任务来的时候,如果已达core核心线程数,则把任务放入队列中
3.当有任务来的时候,如果已达core核心线程数,且队列已满,但已有线程数>最大线程数,则创建线程
4.当有任务来的时候,如果已达core核心线程数,且队列已满,但已有线程数=最大线程数,则执行拒绝策略
一、ThreadPoolExecutor类
java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类。
ThreadPoolExecutor继承了AbstractExecutorService类

1.构造方法4个,及其入参解释
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> 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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
下面解释下一下构造器中各个参数的含义:
1-1.corePoolSize
线程池中会保持corePoolSize个线程数,即是它们是空闲的也不回收。
1-2.maximumPoolSize
线程池允许最大的线程数,它表示在线程池中最多能创建多少个线程。
1-3.keepAliveTime
如果线程池中现存线程数目超过corePoolSize,闲置线程最多等待keepAliveTime的时间就会被终止。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
1-4.unit
参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
1-5.workQueue
一个阻塞队列,用来存储等待执行的任务,这里的阻塞队列有以下几种选择:
1.ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
2.DelayQueue:DelayQueue = BlockingQueue + PriorityQueue + Delayed
3.LinkedBlockingDeque:基于链表的双端阻塞队列
4.LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,内部使用了大量的锁,性能不高,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
5.LinkedTransferQueue:LinkedTransferQueue是 SynchronousQueue 和 LinkedBlockingQueue 的合体,性能比 LinkedBlockingQueue 更高(没有锁操作),比 SynchronousQueue能存储更多的元素。
6.PriorityBlockingQueue:一个具有优先级得无限阻塞队列。
7.SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
1-6.threadFactory
线程工厂,指定线程池创建每个线程是怎样的,是否需要特殊化处理。如果不入这个参数,系统会自动创建。
1-7.handler
拒绝策略,线程池中线程上线满了、有界队列也满了的时候还有新的任务来时怎么办,这里做兜底补锅。这里可以自己写拒绝策略的逻辑,也可以用ThreadPoolExecutor提供的四种拒绝策略:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
2.常用的非构造方法介绍
2-1.execute()
是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
2-2.submit()
是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果。
2-3shutdown()和shutdownNow()
是用来关闭线程池的。
shutdown的原理是只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
shutdownNow的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。shutdownNow会首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。
只要调用了这两个关闭方法的其中一个,isShutdown方法就会返回true。
当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。
至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定。
通常调用shutdown来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow。
还有很多其他的方法:
比如:getQueue() 、getPoolSize() 、getActiveCount()、getCompletedTaskCount()等获取与线程池相关属性的方法
3.代码示例
package com.sid;
import java.util.concurrent.*;
/**
* @program: thread-test
* @description:
* @author: Sid
* @date: 2019-01-15 17:02
* @since: 1.0
**/
//CPU密集型 CPU核数+1 或者 CPU核数*2
//IO密集型 CPU核数/(1-阻塞系数) 阻塞系数一般是0.8-0.9之间
//不是随便哪个人想建一个线程池就建一个,一般是在Util中建,统一管理,提供一个线程池工厂,根据需求创建不同的线程池,基础架构不封装线程池
//用javaBean创建线程池,在destroyMaserd里面销毁的时候调用shutdown.或者自己写个holk,把线程池取出来调用shutdown方法,服务关闭的时候要关线程池
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(5,
Runtime.getRuntime().availableProcessors() * 2,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(200),
//指定创建每个线程是怎样的,是否需要特殊化处理。如果不入这个参数,系统会自动创建
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
//设置这个线程池创建的线程的名字,方便在日志中查看
t.setName("order-thread");
if(t.isDaemon()) {
//设置非守护线程 (根据使用场景)
t.setDaemon(false);
}
//设置线程优先级都一样,防止有人不按期望设置优先级 (根据使用场景)
if(Thread.NORM_PRIORITY != t.getPriority()) {
t.setPriority(Thread.NORM_PRIORITY);
}
return t;
}
},
//线程池中线程上线满了、有界队列也满了的时候还有新的任务来,就会使用拒绝策略,这里做兜底补锅
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.err.println("拒绝策略:" + r);
}
});
pool.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName()+" is runing");
System.out.println(Thread.currentThread().getName()+" will sleep");
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName()+" is go on");
System.out.println(Thread.currentThread().getName()+" is done");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
pool.shutdown();
}
}
二、Executors提供有四种封装好了的线程池
建议使用过ThreadPoolExecutor来创建线程池。
不推荐使用Executors来创建线程池,它有安全隐患,下面每种会详细讲安全隐患在哪儿。
1.newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
不推荐理由:最大线程数无限大。
创建该线程池函数源码:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- corePoolSize =0
- maximumPoolSize为Integer.MAX_VALUE,是一种可以无限扩容的线程池。
- keepAliveTime为60,超过核心线程数的线程空闲时间超过60s就会被杀死;
- 阻塞队列采用SynchronousQueue,不存储元素、没有内部容量、阻塞。每个插入操作必须等待另一个线程的对应移除操作。支持可选的公平排序策略。SynchronousQueue的吞吐量高于LinkedBlockingQueue 和 ArrayBlockingQueue。
该线程池的工作机制则是:
来一个任务就创建一个线程去执行,直到创建到Integer.MAX_VALUE(相当于无限了)个线程,就执行拒绝策略AbortPolicy。
使用代码
package com.sid;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class CachedThreadPoolTest {
public static void main(final String[] arguments) throws InterruptedException {
ExecutorService executor = Executors.newCachedThreadPool();
// Cast the object to its class type
ThreadPoolExecutor pool = (ThreadPoolExecutor) executor;
//Stats before tasks execution
System.out.println("Core threads: " + pool.getCorePoolSize());
System.out.println("Largest executions: " + pool.getLargestPoolSize());
System.out.println("Maximum allowed threads: " + pool.getMaximumPoolSize());
System.out.println("Current threads in pool: " + pool.getPoolSize());
System.out.println("Currently executing threads: " + pool.getActiveCount());
System.out.println("Total number of threads(ever scheduled): " + pool.getTaskCount());
executor.execute(new Task());
executor.submit(new Task());
//Stats after tasks execution
System.out.println("Core threads: " + pool.getCorePoolSize());
System.out.println("Largest executions: " + pool.getLargestPoolSize());
System.out.println("Maximum allowed threads: " + pool.getMaximumPoolSize());
System.out.println("Current threads in pool: " + pool.getPoolSize());
System.out.println("Currently executing threads: " + pool.getActiveCount());
System.out.println("Total number of threads(ever scheduled): " + pool.getTaskCount());
executor.shutdown();
}
static class Task implements Runnable {
public void run() {
try {
Long duration = (long) (Math.random() * 5);
System.out.println("Running Task! Thread Name: " + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(duration);
System.out.println("Task Completed! Thread Name: "+ Thread.currentThread().getName());
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
结果
Core threads: 0
Largest executions: 0
Maximum allowed threads: 2147483647
Current threads in pool: 0
Currently executing threads: 0
Total number of threads(ever scheduled): 0
Core threads: 0
Largest executions: 2
Maximum allowed threads: 2147483647
Current threads in pool: 2
Currently executing threads: 2
Total number of threads(ever scheduled): 2
Running Task! Thread Name: pool-1-thread-1
Running Task! Thread Name: pool-1-thread-2
Task Completed! Thread Name: pool-1-thread-2
Task Completed! Thread Name: pool-1-thread-1
Process finished with exit code 0
2.newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
不推荐理由:虽然线程池中的线程数目是有限且固定的,但是阻塞队列的最大长度为Integer.MAX_VALUE。
创建该线程池函数源码:
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
创建LinkedBlockingQueue阻塞队列方法源码:
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
- FixedThreadPool是一种容量固定的线程池
-最大线程数和core核心线程数一样
- 阻塞队列采用LinkedBlockingQueue,它是一种无界队列
该线程池的工作机制则是:
由于最大线程数和core核心线程数一样(则keepAliveTime无效),那么每来一个任务则创建一个线程去执行,直到达到最大线程数后,再来任务就放到队列中。由于采用的无界队列,队列的长度是Integer.MAX_VALUE,拒绝策略将不会被触发。
代码
只有第一个是详细的
package com.sid;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolTest {
public static void main(String[] args) {
//参数5是线程池定长
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
public void run() {
try {
System.out.println(index+" "+System.currentTimeMillis());
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
fixedThreadPool.shutdown();
}
}
结果
0 1536493672006
1 1536493672006
2 1536493672006
3 1536493672006
4 1536493672006
5 1536493677016
6 1536493677016
9 1536493677016
7 1536493677016
8 1536493677016
Process finished with exit code 0
3.newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。
不推荐理由:线程池中的最大线程数用的Integer.MAX_VALUE。阻塞队列是无界队列。
创建该线程池函数源码:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory);
}
- ScheduledThreadPool接收SchduledFutureTask类型的任务,提交任务的方式有2种;
1. scheduledAtFixedRate;
2. scheduledWithFixedDelay;
- SchduledFutureTask接收参数:
time:任务开始时间
sequenceNumber:任务序号
period:任务执行的时间间隔
-最大线程数为Integer.MAX_VALUE
- 阻塞队列采用DelayQueue,它是一种无界队列;
- DelayQueue内部封装了一个PriorityQueue,它会根据time的先后排序,若time相同,则根据sequenceNumber排序;
- 工作线程执行流程:
1. 工作线程会从DelayQueue中取出已经到期的任务去执行;
2. 执行结束后重新设置任务的到期时间,再次放回DelayQueue。
代码
只有第一个是详细的
延迟3秒执行
package com.sid;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPoolTest {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
//延迟3秒执行
scheduledThreadPool.schedule(new Runnable() {
public void run() {
System.out.println("delay 3 seconds");
}
}, 3, TimeUnit.SECONDS);
}
}
表示延迟1秒后每3秒执行一次
package com.sid;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPoolTest {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
//表示延迟1秒后每3秒执行一次。
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println("delay 1 seconds, and excute every 3 seconds "+System.currentTimeMillis());
}
}, 1, 3, TimeUnit.SECONDS);
}
}
4.newSingleThreadExecutor
创建一个单线程化的线程池,
它只会用唯一的工作线程来执行任务,
保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
不推荐理由:虽然线程池的核心线程数和最大线程数都为1,但是阻塞队列的最大长度为Integer.MAX_VALUE。
创建该线程池的函数源码:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
创建LinkedBlockingQueue阻塞队列的函数源码:
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
阻塞队列采用LinkedBlockingQueue
该线程池的工作机制则是:
由于最大线程数和core核心线程数都是1,线程池里面只有一个线程。如果该线程被使用时,来了任务,则任务进入队列。
代码
只有第一个是详细的
package com.sid;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutorTest {
public static void main(String[] args) {
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
三、合理的配置线程池
从以下几个角度来进行分析:
- 任务的性质:CPU密集型任务,IO密集型任务和混合型任务。
- 任务的优先级:高,中和低。
- 任务的执行时间:长,中和短。
- 任务的依赖性:是否依赖其他系统资源,如数据库连接。
任务性质不同的任务可以用不同规模的线程池分开处理。
CPU密集型任务配置尽可能少的线程数量,比如 CPU核数+1 或者 CPU核数*2
IO密集型任务,最佳线程数 = CPU数量 * CPU利用率 *(线程等待时间/线程CPU时间 + 1)。
通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU核数。
最大线程数=QPS*每次请求处理需要的时间
混合型的任务,如果可以拆分,则将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐率要高于串行执行的吞吐率,如果这两个任务执行时间相差太大,则没必要进行分解。
优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先得到执行,需要注意的是如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。
执行时间不同的任务可以交给不同规模的线程池来处理,或者也可以使用优先级队列,让执行时间短的任务先执行。
使用有界队列,不然会有安全隐患!,有界队列能增加系统的稳定性和预警能力。
四、线程池的监控
通过ThreadPoolExecutor提供的参数进行监控。ThreadPoolExecutor里有一些属性在监控线程池的时候可以使用
completedTaskCount:线程池在运行过程中已完成的任务数量。小于或等于taskCount。
largestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。
getPoolSize():线程池的线程数量。如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不减。
getActiveCount():获取活动的线程数。
通过扩展ThreadPoolExecutor进行监控。

通过继承ThreadPoolExecutor并重写ThreadPoolExecutor的beforeExecute,afterExecute和terminated方法,可以在任务执行前,执行后和线程池关闭前干一些事情。如监控任务的平均执行时间,最大执行时间和最小执行时间等。这几个方法在线程池里是空方法。如:
protected void beforeExecute(Thread t, Runnable r) { }
protected void afterExecute(Runnable r, Throwable t) { }
protected void terminated() { }
2662

被折叠的 条评论
为什么被折叠?



