目录
- 概念
- 原理
- 如何保证线程复用
- 参数
- 规范
- 线程池大小如何确定
- 估算算法
- Demo
- 其他
- 自己实现完整的线程池
- 其他
- 捕获异常信息
概念
线程池是为了提高程序执行效率,尽量减少线程对象的创建和销毁的次数而产生的一种技术。线程池内部维护了两个集合,一个是线程的集合,另一个是任务集合。
线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
原理
① corePoolSize
顾名思义,其指代核心线程的数量。当提交一个任务到线程池时,线程池会创建一个核心线程来执行任务,即使其他空闲的核心线程能够执行新任务也会创建新的核心线程,而等到需要执行的任务数大于线程池核心线程的数量时就不再创建,这里也可以理解为当核心线程的数量等于线程池允许的核心线程最大数量的时候,如果有新任务来,就不会创建新的核心线程。
如果你想要提前创建并启动所有的核心线程,可以调用线程池的prestartAllCoreThreads()方法。
② maximumPoolSize
顾名思义,其指代线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。所以只有队列满了的时候,这个参数才有意义。因此当你使用了无界任务队列的时候,这个参数就没有效果了。
③ keepAliveTime
顾名思义,其指代线程活动保持时间,即当线程池的工作线程空闲后,保持存活的时间。所以,如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率,不然线程刚执行完一个任务,还没来得及处理下一个任务,线程就被终止,而需要线程的时候又再次创建,刚创建完不久执行任务后,没多少时间又终止,会导致资源浪费。
注意:这里指的是核心线程池以外的线程。还可以设置allowCoreThreadTimeout = true这样就会让核心线程池中的线程有了存活的时间。
④ TimeUnit
顾名思义,其指代线程活动保持时间的单位:可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒)。
⑤ workQueue
顾名思义,其指代任务队列:用来保存等待执行任务的阻塞队列。
⑥ threadFactory
顾名思义,其指代创建线程的工厂:可以通过线程工厂给每个创建出来的线程设置更加有意义的名字。
⑦ RejectedExecutionHandler
顾名思义,其指代拒绝执行程序,可以理解为饱和策略:当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。在JDK1.5中Java线程池框架提供了以下4种策略。
AbortPolicy:直接抛出异常RejectedExecutionException。
CallerRunsPolicy:只用调用者所在线程来运行任务,即由调用 execute方法的线程执行该任务。
DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
DiscardPolicy:不处理,丢弃掉,即丢弃且不抛出异常。
这7个参数共同决定了线程池执行一个任务的策略:
当一个任务被添加进线程池时:
线程数量未达到 corePoolSize,则新建一个线程(核心线程)执行任务
线程数量达到了 corePools,则将任务移入队列等待
队列已满,新建线程(非核心线程)执行任务
队列已满,总线程数又达到了 maximumPoolSize,就会由上面那位星期天(RejectedExecutionHandler)抛出异常
说白了就是先利用核心线程,核心线程用完,新来的就加入等待队列,一旦队列满了,那么只能开始非核心线程来执行了。
上面的策略,会在阅读代码的时候体现出来,并且在代码中也能窥探出真正复用空闲线程的实现原理。
接下来我们就从线程池执行任务的入口分析。
一个线程池可以接受任务类型有Runnable和Callable,分别对应了execute和submit方法。目前我们只分析execute的执行过程。
如何保证线程复用
超过核心线程数 允许最大线程数 超时 可以回收
参数
public ThreadPoolExecutor(int corePoolSize,// 线程池核心池的大小。
int maximumPoolSize,//线程池的最大线程数
long keepAliveTime,//当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
TimeUnit unit,//keepAliveTime 的时间单位。
BlockingQueue<Runnable> workQueue,//用来储存等待执行任务的队列
ThreadFactory threadFactory,// 线程工厂
RejectedExecutionHandler handler);//拒绝策略
- corePoolSize的作用是代表要开启核心线程数量,核心线程会一直保留。
- maximumPoolSize的作用是最大可以创建的线程数量,当你的所有核心线程都在工作状态时,此时如果有新的任务需要执行,系统就会创建新的线程来执行任务。
- keepAliveTime代表新开启的线程如果执行完毕后可以存活多长时间,如果在设置的时间内没有任务使用该线程,则线程资源就会归还操作系统。
- timeUnit 代表线程存活的时间单位。
- workQueue 任务队列,如果正在执行的任务超过了最大线程数,可以存放在队列中,当线程池中有空闲资源就可以从队列中取出任务继续执行。
队列类型有如下几种LinkedBlockingQueue ArrayBlockingQueue SynchronousQueue TransferQueue,使用不同的队列就会产生不同类型的线程池。(任务队列的知识,我会单独的去写一篇文章) - threadFactory 线程工厂,他的作用是用来产生线程的,可以自定义线程的类型,比如我们可以定义线程组名称,在jstack问题排查时,非常有帮助。
- rejectedExecutionHandler 拒绝策略, 当所有线程都在忙,并且任务队列处于满任务的状态,则会执行拒绝策略。
有请求时,创建线程执行任务,当线程数量等于corePoolSize时,请求加入阻塞队列里,当队列满了时,接着创建线程,线程数等于maximumPoolSize。 当任务处理不过来的时候,线程池开始执行拒绝策略。
阻塞队列:
ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
DelayQueue: 一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue: 一个不存储元素的阻塞队列。
LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列。
拒绝策略:
ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。 (默认)
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务。(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。
规范
线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资
源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者
“过度切换”的问题。
线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样
的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
我们发现newFixedThreadPool和newSingleThreadExecutor方法他们都使用了LinkedBlockingQueue的任务队列,LinkedBlockingQueue的默认大小为Integer.MAX_VALUE。而newCachedThreadPool中定义的线程池大小为Integer.MAX_VALUE。
所以阿里禁止使用Executors创建线程池的原因就是FixedThreadPool和SingleThreadPool的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
CachedThreadPool允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
正例:
public class TimerTaskThread extends Thread {
public TimerTaskThread() {
super.setName("TimerTaskThread");
...
}
线程池大小如何确定
此概念适用于固定线程池,在该线程池中,我们可以选择提供线程数,并且您应该执行许多任务。
理想情况下,没有固定的数字可用于确定线程池的数目。这一切都取决于Java程序的用例。
因此,在决定线程池大小时应考虑两个因素
1.CPU核心数
一个CPU内核一次将运行一个线程。让我们暂时忽略超线程。
N核心= N线程
它只是意味着更多的核心,然后更多的并行性。
如果是超线程,请考虑逻辑处理器-
int poolSize = Runtime.getRuntime().availableProcessors();
Linux
总核数 = 物理CPU个数 X 每颗物理CPU的核数
总逻辑CPU数 = 物理CPU个数 X 每颗物理CPU的核数 X 超线程数
查看物理CPU个数
cat /proc/cpuinfo| grep “physical id”| sort| uniq| wc -l
查看每个物理CPU中core的个数(即核数)
cat /proc/cpuinfo| grep “cpu cores”| uniq
查看逻辑CPU的个数
cat /proc/cpuinfo| grep “processor”| wc -l
2.任务类型
CPU绑定
假设有一个CPU内核,一个线程正在运行,并且提交了2个任务。然后将一个任务提交到线程一,一旦完成,则提交另一任务。在提交两个任务之间,不应有任何时间间隔以实现CPU的最大利用率。
CPU最大利用率
如果在一个核心CPU中有两个线程,那么CPU占用的时间与在一个线程中占用的时间相同。因此,在一个核心CPU中创建太多线程不会对性能产生任何影响。因此,在CPU绑定任务的情况下,线程数应等于CPU的内核数。
IO绑定任务
涉及通过网络 调用与其他应用程序通信的任务,例如数据库,Web服务,微服务,外部缓存等。
在上图中,只有一个线程,并且已向其提交了一个任务。当此任务等待IO操作完成时,CPU将变得空闲且效率低下。IO调用给出响应后,它将再次开始工作,直到任务完成。
在空闲时间或等待时间中,我们可以再启动一个线程并使它运行,以便获得CPU的最大输出,并且一个线程可以处于等待状态,直到从IO调用接收到输出为止。
因此,在具有一个核心CPU的IO绑定任务的情况下,我们可以运行以增加线程数,并获得CPU的最大利用率。因此,线程数取决于完成一项任务IO操作所花费的时间。这样,在CPU中有多个内核的情况下,我们可以应用相同的概念
理想线程数=内核数 [1+(等待io完成时间/ CPU时间)]*
其中(等待io完成时间/ CPU时间),这称为阻塞系数。
其他因素
您应该检查其他应用程序是否在同一CPU上运行。
是否在同一JVM上运行任何其他线程。
同一CPU中的线程过多可能会导致性能降低。
估算算法
https://github.com/sunshanpeng/dark_magic
package pool_size_calculate;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.BlockingQueue;
/**
* A class that calculates the optimal thread pool boundaries. It takes the
* desired target utilization and the desired work queue memory consumption as
* input and retuns thread count and work queue capacity.
*
* @author Niklas Schlimm
*
*/
public abstract class PoolSizeCalculator {
/**
* The sample queue size to calculate the size of a single {@link Runnable}
* element.
*/
private final int SAMPLE_QUEUE_SIZE = 1000;
/**
* Accuracy of test run. It must finish within 20ms of the testTime
* otherwise we retry the test. This could be configurable.
*/
private final int EPSYLON = 20;
/**
* Control variable for the CPU time investigation.
*/
private volatile boolean expired;
/**
* Time (millis) of the test run in the CPU time calculation.
*/
private final long testtime = 3000;
/**
* Calculates the boundaries of a thread pool for a given {@link Runnable}.
*
* @param targetUtilization
* the desired utilization of the CPUs (0 <= targetUtilization <= * 1) * @param targetQueueSizeBytes * the desired maximum work queue size of the thread pool (bytes) */ protected void calculateBoundaries(BigDecimal targetUtilization, BigDecimal targetQueueSizeBytes) { calculateOptimalCapacity(targetQueueSizeBytes); Runnable task = creatTask(); start(task); start(task); // warm up phase long cputime = getCurrentThreadCPUTime(); start(task); // test intervall cputime = getCurrentThreadCPUTime() - cputime; long waittime = (testtime * 1000000) - cputime; calculateOptimalThreadCount(cputime, waittime, targetUtilization); } private void calculateOptimalCapacity(BigDecimal targetQueueSizeBytes) { long mem = calculateMemoryUsage(); BigDecimal queueCapacity = targetQueueSizeBytes.divide(new BigDecimal( mem), RoundingMode.HALF_UP); System.out.println("Target queue memory usage (bytes): " + targetQueueSizeBytes); System.out.println("createTask() produced " + creatTask().getClass().getName() + " which took " + mem + " bytes in a queue"); System.out.println("Formula: " + targetQueueSizeBytes + " / " + mem); System.out.println("* Recommended queue capacity (bytes): " + queueCapacity); } /** * Brian Goetz' optimal thread count formula, see 'Java Concurrency in * Practice' (chapter 8.2) * * @param cpu * cpu time consumed by considered task * @param wait * wait time of considered task * @param targetUtilization * target utilization of the system */ private void calculateOptimalThreadCount(long cpu, long wait, BigDecimal targetUtilization) { BigDecimal waitTime = new BigDecimal(wait); BigDecimal computeTime = new BigDecimal(cpu); BigDecimal numberOfCPU = new BigDecimal(Runtime.getRuntime() .availableProcessors()); BigDecimal optimalthreadcount = numberOfCPU.multiply(targetUtilization) .multiply( new BigDecimal(1).add(waitTime.divide(computeTime, RoundingMode.HALF_UP))); System.out.println("Number of CPU: " + numberOfCPU); System.out.println("Target utilization: " + targetUtilization); System.out.println("Elapsed time (nanos): " + (testtime * 1000000)); System.out.println("Compute time (nanos): " + cpu); System.out.println("Wait time (nanos): " + wait); System.out.println("Formula: " + numberOfCPU + " * " + targetUtilization + " * (1 + " + waitTime + " / " + computeTime + ")"); System.out.println("* Optimal thread count: " + optimalthreadcount); } /** * Runs the {@link Runnable} over a period defined in {@link #testtime}. * Based on Heinz Kabbutz' ideas * (http://www.javaspecialists.eu/archive/Issue124.html). * * @param task * the runnable under investigation */ public void start(Runnable task) { long start = 0; int runs = 0; do { if (++runs > 5) {
throw new IllegalStateException("Test not accurate");
}
expired = false;
start = System.currentTimeMillis();
Timer timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
expired = true;
}
}, testtime);
while (!expired) {
task.run();
}
start = System.currentTimeMillis() - start;
timer.cancel();
} while (Math.abs(start - testtime) > EPSYLON);
collectGarbage(3);
}
private void collectGarbage(int times) {
for (int i = 0; i < times; i++) {
System.gc();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
/**
* Calculates the memory usage of a single element in a work queue. Based on
* Heinz Kabbutz' ideas
* (http://www.javaspecialists.eu/archive/Issue029.html).
*
* @return memory usage of a single {@link Runnable} element in the thread
* pools work queue
*/
public long calculateMemoryUsage() {
BlockingQueue queue = createWorkQueue();
for (int i = 0; i < SAMPLE_QUEUE_SIZE; i++) {
queue.add(creatTask());
}
long mem0 = Runtime.getRuntime().totalMemory()
- Runtime.getRuntime().freeMemory();
long mem1 = Runtime.getRuntime().totalMemory()
- Runtime.getRuntime().freeMemory();
queue = null;
collectGarbage(15);
mem0 = Runtime.getRuntime().totalMemory()
- Runtime.getRuntime().freeMemory();
queue = createWorkQueue();
for (int i = 0; i < SAMPLE_QUEUE_SIZE; i++) {
queue.add(creatTask());
}
collectGarbage(15);
mem1 = Runtime.getRuntime().totalMemory()
- Runtime.getRuntime().freeMemory();
return (mem1 - mem0) / SAMPLE_QUEUE_SIZE;
}
/**
* Create your runnable task here.
*
* @return an instance of your runnable task under investigation
*/
protected abstract Runnable creatTask();
/**
* Return an instance of the queue used in the thread pool.
*
* @return queue instance
*/
protected abstract BlockingQueue createWorkQueue();
/**
* Calculate current cpu time. Various frameworks may be used here,
* depending on the operating system in use. (e.g.
* http://www.hyperic.com/products/sigar). The more accurate the CPU time
* measurement, the more accurate the results for thread count boundaries.
*
* @return current cpu time of current thread
*/
protected abstract long getCurrentThreadCPUTime();
}
我的任务
package pool_size_calculate;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.management.ManagementFactory;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class SimplePoolSizeCaculatorImpl extends PoolSizeCalculator {
@Override
protected Runnable creatTask() {
return new AsyncIOTask();
}
@Override
protected BlockingQueue createWorkQueue() {
return new LinkedBlockingQueue(1000);
}
@Override
protected long getCurrentThreadCPUTime() {
return ManagementFactory.getThreadMXBean().getCurrentThreadCpuTime();
}
public static void main(String[] args) {
PoolSizeCalculator poolSizeCalculator = new SimplePoolSizeCaculatorImpl();
poolSizeCalculator.calculateBoundaries(new BigDecimal(1.0), new BigDecimal(100000));
}
}
/**
* 自定义的异步IO任务
* @author Will
*
*/
class AsyncIOTask implements Runnable {
@Override
public void run() {
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
String getURL = "http://baidu.com";
URL getUrl = new URL(getURL);
connection = (HttpURLConnection) getUrl.openConnection();
connection.connect();
reader = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
// empty loop
}
}
catch (IOException e) {
} finally {
if(reader != null) {
try {
reader.close();
}
catch(Exception e) {
}
}
connection.disconnect();
}
}
}
结果
Target queue memory usage (bytes): 100000
createTask() produced pool_size_calculate.AsyncIOTask which took 40 bytes in a queue
Formula: 100000 / 40
- Recommended queue capacity (bytes): 2500
Number of CPU: 4
Target utilization: 1
Elapsed time (nanos): 3000000000
Compute time (nanos): 47181000
Wait time (nanos): 2952819000
Formula: 4 * 1 * (1 + 2952819000 / 47181000) - Optimal thread count: 256
推荐的任务队列大小为2500,线程数为256,有点出乎意料之外。我可以如下构造一个线程池:
ThreadPoolExecutor pool =
new ThreadPoolExecutor(256, 256, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(2500));
Demo
/**
* 自定义线程名称,方便的出错的时候溯源
*/
private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("test-pool-%d").build();
/**
* corePoolSize 线程池核心池的大小
* maximumPoolSize 线程池中允许的最大线程数量
* keepAliveTime 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间
* unit keepAliveTime 的时间单位
* workQueue 用来储存等待执行任务的队列
* threadFactory 创建线程的工厂类
* handler 拒绝策略类,当线程池数量达到上线并且workQueue队列长度达到上限时就需要对到来的任务做拒绝处理
*/
private static ExecutorService service = new ThreadPoolExecutor(
4,
40,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1024),
namedThreadFactory,
new ThreadPoolExecutor.AbortPolicy()
);
/**
* 获取线程池
* @return 线程池
*/
public static ExecutorService getEs() {
return service;
}
/**
* 使用线程池创建线程并异步执行任务
* @param r 任务
*/
public static void newTask(Runnable r) {
service.execute(r);
}
其他
shutdown只是将线程池的状态设置为SHUTWDOWN状态,正在执行的任务会继续执行下去,没有被执行的则中断。
而shutdownNow则是将线程池的状态设置为STOP,正在执行的任务则被停止,没被执行任务的则返回。
ThreadLocal 无法解决共享对象的更新问题,ThreadLocal 对象建议使用 static
修饰。这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享
此静态变量 ,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只
要是这个线程内定义的)都可以操控这个变量。
自己实现完整的线程池
package com.alex.example.book.thread;
import io.netty.util.concurrent.DefaultThreadFactory;
import java.util.ArrayDeque;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author Hasee
* @ClassName: ThreadPool
* @Description:
* @date 2021/5/6
*/
public interface ThreadPool {
void execute(Runnable runnable);
void shutdown();
int getInitSize();
int getMaxSize();
int getCoreSize();
int getQueueSize();
int getActiveCount();
boolean isShutdown();
}
interface RunnableQueue {
void offer(Runnable runnable);
Runnable take() throws InterruptedException;
int size();
}
interface ThreadFactory {
Thread createThread(Runnable runnable);
}
@FunctionalInterface
interface DenyPolicy {
void reject(Runnable runnable, ThreadPool threadPool);
//直接将任务丢弃
class DiscardDenyPolicy implements DenyPolicy {
@Override
public void reject(Runnable runnable, ThreadPool threadPool) {
//do nothing
}
}
//向任务提交者抛出异常
class AbortDenyPolicy implements DenyPolicy {
@Override
public void reject(Runnable runnable, ThreadPool threadPool) {
}
}
//在提交者所在的线程中执行任务
class RunnerDenyPolicy implements DenyPolicy {
@Override
public void reject(Runnable runnable, ThreadPool threadPool) {
if (!threadPool.isShutdown()) {
runnable.run();
}
}
}
}
//通知提交者 任务队列无法接收新任务
class RunnableDenyException extends RuntimeException {
public RunnableDenyException(String message) {
super(message);
}
}
//线程池内部 不断从queue中取出某个runnable 运行run方法
class InternalTask implements Runnable {
private final RunnableQueue runnableQueue;
private volatile boolean running = true;
public InternalTask(RunnableQueue runnableQueue) {
this.runnableQueue = runnableQueue;
}
@Override
public void run() {
//任务为running状态 没有中断 不断从queue中获取runnable 执行run方法
while (running && !Thread.currentThread().isInterrupted()) {
try {
Runnable task = runnableQueue.take();
task.run();
} catch (InterruptedException e) {
running = false;
break;
}
}
}
//停止当前任务 在线程池的shutdown方法中使用
public void stop() {
this.running = false;
}
}
class LinkedRunnableQueue implements RunnableQueue {
//队列的最大容量
private final int limit;
//任务满了 需要执行拒绝策略
private final DenyPolicy denyPolicy;
//任务队列
private final LinkedList<Runnable> runnableList = new LinkedList<>();
private final ThreadPool threadPool;
public LinkedRunnableQueue(int limit, DenyPolicy denyPolicy, ThreadPool threadPool) {
this.limit = limit;
this.denyPolicy = denyPolicy;
this.threadPool = threadPool;
}
@Override
public void offer(Runnable runnable) {
synchronized (runnableList) {
if (runnableList.size() >= limit) {
//无法容纳新的任务时执行拒绝策略
denyPolicy.reject(runnable, threadPool);
} else {
//将任务加入到队尾 唤醒阻塞中的线程
runnableList.addLast(runnable);
runnableList.notifyAll();
}
}
}
@Override
public Runnable take() throws InterruptedException {
synchronized (runnableList) {
while (runnableList.isEmpty()) {
try {
//如果任务队列中没有可执行任务 则当前线程将会挂起 进入runnableList 关联的monitor waitset 中等待唤醒(新的任务加入)
runnableList.wait();
} catch (InterruptedException e) {
//被中断需要将该异常抛出
throw e;
}
}
}
//从任务队列头部移除一个任务
return runnableList.removeFirst();
}
@Override
public int size() {
synchronized (runnableList) {
return runnableList.size();
}
}
}
class BasicThreadPool extends Thread implements ThreadPool {
private static class ThreadTask {
Thread thread;
public InternalTask interalTask;
public ThreadTask(Thread thread, InternalTask internalTask) {
this.thread = thread;
this.interalTask = internalTask;
}
}
private static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger GROUP_COUNTER = new AtomicInteger(1);
private static final ThreadGroup group = new ThreadGroup("MyThreadPool-" + GROUP_COUNTER.getAndIncrement());
private static final AtomicInteger COUNTER = new AtomicInteger(0);
@Override
public Thread createThread(Runnable runnable) {
return new Thread(group, runnable, "thread-pool-" + COUNTER.getAndIncrement());
}
}
//初始化线程数量
private int initSize;
private int maxSize;
private int coreSize;
private int activeCount;
private ThreadFactory threadFactory;
private RunnableQueue runnableQueue;
private volatile boolean isShutdown = false;
private final Queue<ThreadTask> threadTaskQueue = new ArrayDeque<>();
private final static DenyPolicy DEFAULT_DENY_POLICY = new DenyPolicy.DiscardDenyPolicy();
private final static ThreadFactory DEFAULT_THREAD_POLICY = new DefaultThreadFactory();
private long keepAliveTime;
private TimeUnit timeUnit;
public BasicThreadPool(int initSize, int maxSize, int coreSize, int queueSize) {
this(initSize, maxSize, coreSize, DEFAULT_THREAD_POLICY, queueSize, DEFAULT_DENY_POLICY, 10,
TimeUnit.SECONDS);
}
public BasicThreadPool(int initSize, int maxSize, int coreSize, ThreadFactory threadFactory,
int queueSize, DenyPolicy denyPolicy, long keepAliveTime, TimeUnit timeUnit) {
this.initSize = initSize;
this.maxSize = maxSize;
this.coreSize = coreSize;
this.threadFactory = threadFactory;
this.runnableQueue = new LinkedRunnableQueue(queueSize, denyPolicy, this);
this.keepAliveTime = keepAliveTime;
this.timeUnit = timeUnit;
this.init();
}
private void init() {
start();
for (int i = 0; i < initSize; i++) {
newThread();
}
}
private void newThread() {
InternalTask internalTask = new InternalTask(runnableQueue);
Thread thread = this.threadFactory.createThread(internalTask);
ThreadTask threadTask = new ThreadTask(thread, internalTask);
threadTaskQueue.offer(threadTask);
this.activeCount++;
thread.start();
}
@Override
public void execute(Runnable runnable) {
if (this.isShutdown)
throw new IllegalStateException("the thread pool is destory ");
this.runnableQueue.offer(runnable);
}
private void removeThread() {
ThreadTask threadTask = threadTaskQueue.remove();
threadTask.interalTask.stop();
this.activeCount--;
}
@Override
public void shutdown() {
}
@Override
public int getInitSize() {
if (!isShutdown)
throw new IllegalStateException("the thread pool is destory");
return this.initSize;
}
@Override
public int getMaxSize() {
if (isShutdown)
throw new IllegalStateException("The thread pool is destory");
return this.maxSize;
}
@Override
public int getCoreSize() {
if (isShutdown)
throw new IllegalStateException("The thread pool is destory");
return this.coreSize;
}
@Override
public int getQueueSize() {
if (isShutdown)
throw new IllegalStateException(" The thread pool is destory");
return runnableQueue.size();
}
@Override
public int getActiveCount() {
synchronized (this) {
return this.activeCount;
}
}
@Override
public boolean isShutdown() {
synchronized (this) {
if (isShutdown) return true;
isShutdown = true;
threadTaskQueue.forEach(threadTask -> {
threadTask.interalTask.stop();
threadTask.thread.interrupt();
});
this.interrupt();
}
return false;
}
@Override
public void run() {
//维护线程数量 扩容 回收
while (!isShutdown && isInterrupted()) {
try {
timeUnit.sleep(keepAliveTime);
} catch (InterruptedException e) {
isShutdown = true;
break;
}
synchronized (this) {
if (isShutdown) break;
//当前的队列中有任务尚未处理 actinveCount<coreSize 则继续扩容
if (runnableQueue.size() > 0 && activeCount < coreSize) {
for (int i = initSize; i < coreSize; i++) {
newThread();
}
//不想让线程的扩容直接达到maxsize
continue;
}
//当前的队列中有任务尚未处理 activeCount<maxSize 继续扩容
if (runnableQueue.size() > 0 && activeCount < maxSize) {
for (int i = coreSize; i < maxSize; i++) {
newThread();
}
}
//任务队列中没有任务 需要回收 回收到 coreSize即可
if (runnableQueue.size() == 0 && activeCount > coreSize) {
for (int i = coreSize; i < activeCount; i++) {
removeThread();
}
}
}
}
}
}
class ThreadPoolTest {
public static void main(String[] args) throws InterruptedException {
final ThreadPool threadPool = new BasicThreadPool(2, 6, 4, 1000);
for (int i = 0; i < 20; i++) {
threadPool.execute(() -> {
try {
TimeUnit.SECONDS.sleep(10);
System.out.println(Thread.currentThread().getName() + " is running and done.");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
for (; ; ) {
System.out.println("getActiveCount:" + threadPool.getActiveCount());
threadPool.getQueueSize();
threadPool.getCoreSize();
threadPool.getMaxSize();
TimeUnit.SECONDS.sleep(5);
TimeUnit.SECONDS.sleep(12);
threadPool.shutdown();
Thread.currentThread().join();
}
}
}
其他
捕获异常信息
当单线程的程序发生一个未捕获的异常时我们可以采用try…catch进行异常的捕获,但是在多线程环境中,线程抛出的异常是不能用try…catch捕获的,这样就有可能导致一些问题的出现,比如异常的时候无法回收一些系统资源,或者没有关闭当前的连接等等。
首先来看一个示例:
package com.exception;
public class NoCaughtThread
{
public static void main(String[] args)
{
try
{
Thread thread = new Thread(new Task());
thread.start();
}
catch (Exception e)
{
System.out.println("==Exception: "+e.getMessage());
}
}
}class Task implements Runnable
{
@Override
public void run()
{
System.out.println(3/2);
System.out.println(3/0);
System.out.println(3/1);
}
}
运行结果:
1
Exception in thread “Thread-0” java.lang.ArithmeticException: / by zero
at com.exception.Task.run(NoCaughtThread.java:25)
at java.lang.Thread.run(Unknown Source)
可以看到在多线程中通过try…catch试图捕获线程的异常是不可取的。
Thread的run方法是不抛出任何检查型异常的,但是它自身却可能因为一个异常而被中止,导致这个线程的终结。
首先介绍一下如何在线程池内部构建一个工作者线程,如果任务抛出了一个未检查异常,那么它将使线程终结,但会首先通知框架该现场已经终结。然后框架可能会用新的线程来代替这个工作线程,也可能不会,因为线程池正在关闭,或者当前已有足够多的线程能满足需要。当编写一个向线程池提交任务的工作者类线程类时,或者调用不可信的外部代码时(例如动态加载的插件),使用这些方法中的某一种可以避免某个编写得糟糕的任务或插件不会影响调用它的整个线程。
package com.exception;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class InitiativeCaught
{
public void threadDeal(Runnable r, Throwable t)
{
System.out.println("==Exception: "+t.getMessage());
}
class InitialtiveThread implements Runnable
{
@Override
public void run()
{
Throwable thrown = null;
try
{
System.out.println(3/2);
System.out.println(3/0);
System.out.println(3/1);
}
catch(Throwable e)
{
thrown =e;
}
finally{
threadDeal(this,thrown);
}
}
}
public static void main(String[] args)
{
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new InitiativeCaught().new InitialtiveThread());
exec.shutdown();
}
}
运行结果:
1
==Exception: / by zero
上面介绍了一种主动方法来解决未检测异常。在Thread ApI中同样提供了UncaughtExceptionHandle,它能检测出某个由于未捕获的异常而终结的情况。这两种方法是互补的,通过将二者结合在一起,就能有效地防止线程泄露问题。
如下:
package com.exception;
import java.lang.Thread.UncaughtExceptionHandler;
public class WitchCaughtThread
{
public static void main(String args[])
{
Thread thread = new Thread(new Task());
thread.setUncaughtExceptionHandler(new ExceptionHandler());
thread.start();
}
}class ExceptionHandler implements UncaughtExceptionHandler
{
@Override
public void uncaughtException(Thread t, Throwable e)
{
System.out.println("==Exception: "+e.getMessage());
}
}
运行结果:1
==Exception: / by zero
同样可以为所有的Thread设置一个默认的UncaughtExceptionHandler,通过调用Thread.setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)方法,这是Thread的一个static方法。如下:
package com.exception;
import java.lang.Thread.UncaughtExceptionHandler;
public class WitchCaughtThread
{
public static void main(String args[])
{
Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler());
Thread thread = new Thread(new Task());
thread.start();
}
}class ExceptionHandler implements UncaughtExceptionHandler
{
@Override
public void uncaughtException(Thread t, Throwable e)
{
System.out.println("==Exception: "+e.getMessage());
}
}运行结果:
1
==Exception: / by zero如果采用线程池通过execute的方法去捕获异常,先看下面的例子:
public class ExecuteCaught
{
public static void main(String[] args)
{
ExecutorService exec = Executors.newCachedThreadPool();
Thread thread = new Thread(new Task());
thread.setUncaughtExceptionHandler(new ExceptionHandler());
exec.execute(thread);
exec.shutdown();
}
}
ExceptionHandler可参考上面的例子,运行结果:1
Exception in thread “pool-1-thread-1” java.lang.ArithmeticException: / by zero
at com.exception.Task.run(NoCaughtThread.java:25)
at java.lang.Thread.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
可以看到并未捕获到异常。这时需要将异常的捕获封装到Runnable或者Callable中,如下所示:
package com.exception;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ExecuteCaught
{
public static void main(String[] args)
{
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new ThreadPoolTask());
exec.shutdown();
}
}class ThreadPoolTask implements Runnable
{
@Override
public void run()
{
Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler());
System.out.println(3/2);
System.out.println(3/0);
System.out.println(3/1);
}
}运行结果:
==Exception: / by zero
只有通过execute提交的任务,才能将它抛出的异常交给UncaughtExceptionHandler,而通过submit提交的任务,无论是抛出的未检测异常还是已检查异常,都将被认为是任务返回状态的一部分。如果一个由submit提交的任务由于抛出了异常而结束,那么这个异常将被Future.get封装在ExecutionException中重新抛出。
下面两个例子:package com.exception;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class SubmitCaught
{
public static void main(String[] args)
{
ExecutorService exec = Executors.newCachedThreadPool();
exec.submit(new Task());
exec.shutdown();
}
}
package com.exception;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class SubmitCaught
{
public static void main(String[] args)
{
ExecutorService exec = Executors.newCachedThreadPool();
exec.submit(new ThreadPoolTask());
exec.shutdown();
}
}
运行结果都是:这样可以证实我的观点。接下来通过这个例子可以看到捕获的异常:
package com.exception;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;public class SubmitCaught
{
public static void main(String[] args)
{
ExecutorService exec = Executors.newCachedThreadPool();
Future<?> future = exec.submit(new Task());
exec.shutdown();
try
{
future.get();
}
catch (InterruptedException | ExecutionException e)
{
System.out.println("==Exception: "+e.getMessage());
}
}
}
运行结果:==Exception: java.lang.ArithmeticException: / by zero