一、线程池的状态
RUNNING: 可以接收新任务;可以处理阻塞队列任务
SHUTDOWN: 不会接收新任务;但会处理阻塞队列剩余任务
STOP: 会中断正在执行的任务;并抛弃阻塞队列任务
TIDYING: 任务全部执行完毕,活动线程为0,即将进入终结
TERMINATED: 终结
二、ThreadPoolExecutor构造方法
ThreadPoolExecutor是jdk提供的线程池实现,先看一下全参数的构造方法
public ThreadPoolExecutor(int corePoolSize,//核心线程数
int maximumPoolSize,//最大线程数
long keepAliveTime,//救急线程空闲时间
TimeUnit unit,//空闲时间单位
BlockingQueue<Runnable> workQueue,//任务队列
ThreadFactory threadFactory,//线程工厂
RejectedExecutionHandler handler // 拒绝策略) {
//构造方法
}
救急线程:核心线程全部繁忙任务队列也满了,就会启动救急线程来执行新添加到线程池的任务,当救急线程的空闲时间到达给定的时间时就会自动结束
救急线程数=最大线程数-核心线程数
任务队列:当线程池核心线程繁忙时存储提交到线程池的新任务
线程工厂: 给线程池指定创建线程的方式
拒绝策略:线程全部繁忙,任务队列也满时线程池怎样处理新添加的任务
public interface ThreadFactory {
/**
* Constructs a new {@code Thread}. Implementations may also initialize
* priority, name, daemon status, {@code ThreadGroup}, etc.
*
* @param r a runnable to be executed by new thread instance
* @return constructed thread, or {@code null} if the request to
* create a thread is rejected
*/
Thread newThread(Runnable r);
}
示例
以下创建了一个线程,核心线程数是1,救急线程1,任务队列的容量是1,使用默认的线程工厂和拒绝策略
public class Test8 {
static Logger LOG = LoggerFactory.getLogger(Test8.class);
public static void main(String[] args) {
ThreadPoolExecutor executor =
new ThreadPoolExecutor(1,2,0, TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>(1));
executor.execute(new Runnable() {
@Override
public void run() {
LOG.info("AAA");
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
executor.execute(new Runnable() {
@Override
public void run() {
LOG.info("BBB");
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//这个任务线程池会创建救急线程执行,而B还在任务队列中等待,所以C会先执行
executor.execute(new Runnable() {
@Override
public void run() {
LOG.info("CCC");
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
注意下线程池这几个参数的含义,
救急线程: 虽然从定义看指的是核心线程全忙碌,队列也满了才会启动救急线程,但当创建一个没有核心线程的线程池时,还是会先启动一个线程然后才把剩余的任务往队列放
@Slf4j
public class ThreadPoolTest {
public static void main(String[] args) throws InterruptedException {
//创建一个没有核心线程,有2个救急线程,队列容量是1的线程池,
ThreadPoolExecutor executor = new ThreadPoolExecutor(0, 2, 2000,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1));
//提交第一个任务,会启动一个救急线程
executor.execute(new Runnable() {
@Override
public void run() {
log.info("a");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread.sleep(2000);
//2s后提交第2个任务,会被放进队列
executor.execute(new Runnable() {
@Override
public void run() {
log.info("b");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//提交第三个任务,因为队列已满会启动第二个救急线程,
//最终的现象就是 先输出a,再输出c,最后输出b
executor.execute(new Runnable() {
@Override
public void run() {
log.info("c");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
核心线程数:需要注意的是,如果当前运行的线程数小于核心线程数,那么即使已经启动的核心线程空闲,也会启动一个新核心线程来处理任务。原理是某个核心线程处理完自己的任务后它就会去阻塞队列里拿任务,因为阻塞队列现在还是空的所以它被阻塞住了,只有给阻塞队列中添加任务后它才会被唤醒,所以此时要继续启动下一个核心线程来处理任务。
@Slf4j
public class ThreadPoolTest {
public static void main(String[] args) throws InterruptedException {
//创建一个2个核心线程,队列容量是1的线程池,
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 2000,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1));
//提交第一个任务
executor.execute(new Runnable() {
@Override
public void run() {
log.info("a");
}
});
Thread.sleep(2000);
//2s后提交第2个任务,上边那个线程被阻塞住了,需要启动第2个核心线程来执行此任务
executor.execute(new Runnable() {
@Override
public void run() {
log.info("b");
try {
Thread.sleep(500000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//提交第三个任务,会加入阻塞队列,
// 从而唤醒执行第1个任务完成后被队列阻塞的线程来执行这个任务
//最终的现象就是 先输出a,c是一个线程执行的,b是另一个线程执行的
executor.execute(new Runnable() {
@Override
public void run() {
log.info("c");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
--控制台输出
15:42:34.553 [pool-1-thread-1] INFO com.lyy.service.thread.ThreadPoolTest - a
15:42:36.538 [pool-1-thread-2] INFO com.lyy.service.thread.ThreadPoolTest - b
15:42:36.538 [pool-1-thread-1] INFO com.lyy.service.thread.ThreadPoolTest - c
三、Executors工具类中创建线程池的方法
3.1 newFixedThreadPool(int nThreads) 创建固定大小的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
从这个方法的源码可以看出这样创建的线程池的特点:
没有救急线程,任务队列是无界的(容量是Integer类型的最大值),任务较多时有可能造成内存溢出;使用的是默认的拒绝策略
3.2 newCachedThreadPool 带缓冲的线程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
这个线程池中的所有线程都是救急线程,救急线程的个数是Integer的最大值任务执行完后1min内没有新的任务就会结束运行。
SynchronousQueue 没有容量,没有线程来取是放不进去的,没有线程取put方法就会阻塞住。
这个线程池的特点是可以创建无限多的线程来执行任务,任务执行完后线程可以自动结束。
适合任务数比较密集,但每个任务执行时间较短的情况。
3.3 newSingleThreadExecutor 单线程线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
救急线程数为0,核心线程是1
场景:希望多个任务排队执行,线程数固定为1其余任务会放入无界队列排队。
和自己创建一个线程来执行任务的区别:
自己创建的线程如果任务中发生异常线程就会结束而没有补救措施,其余任务就不能执行了;线程池中如果线程挂掉了会启动新的线程来保证池的正常工作,后续任务还可以继续执行。
和new newFixedThreadPool(1)的区别:
返回的对象不同,new newFixedThreadPool(1)对外暴露的是ThreadPoolExecutor,还可以通过set方法修改核心线程数,而newSingleThreadExecutor返回的对象不能被修改。
四、拒绝策略
当任务队列满了,核心线程和救急线程都繁忙时,线程会应用拒绝策略
4.1 AbortPolicy
抛出异常
4.2 CallerRunsPolicy
调用者自己来执行任务
4.3 DiscardPolicy
放弃新提交任务,什么都不做
4.4 DiscardOldestPolicy
丢弃任务队列中等待时间最长的一个任务,把当前任务提交进去
五、提交任务的方法
5.1 void execute
5.2 Future submit(Callable task)
5.3 List<Future> invokeAll(Collection<? extends Callable> tasks)
可以用来利用返回的Future对象等待多个任务执行完
5.4 invokeAny(Collection<? extends Callable> tasks)
可以用来利用返回的Future对象等待其中时间最短的任务执行完,其余的任务会被丢弃,包含任务队列中的任务和正在执行的任务(被中断)
六、关闭线程池的方法
6.1 shutdown
线程池状态变为SHUTDOWN,不会接收新任务,但已提交的任务(任务队列中的任务)会执行完,不会阻塞调用线程的执行。
6.2 shutdownNow
线程池状态变为STOP,不会接收新任务,会将队列中的任务返回,不再执行,用interrupt中断正在执行的任务