为什么要用线程池
1.线程池可以节约创建线程和销毁线程的时间。
2.线程池可以对线程进行管理。
3.线程池把执行机制和工作单元分离。工作单元就是Runnable和Callable,执行单元就是由Executor框架提供。
Java中的线程池
线程池的核心接口与类
核心接口是Executor和ExecutorService。ExecutorService就是线程池的接口定义。
Executor框架的结构
Executor框架主要是三个部分。
1.任务
Callable和Runnable,这两个就是任务,可以提交给线程池执行
2.任务的执行
任务执行的核心接口Executor,以及继承自Executor的ExecutorService接口。
3.异步计算的结果
Future接口和实现类FutureTask
ThreadPoolExecutor
这个类是Java中提供的线程池的线程池的具体实现。这个类的构造方法总共有4个,参数的话就更多了。这里就不列出构造方法的方法签名了,说一下主要的参数即可。
corePoolSize:核心线程池数量
maximumPoolSize:最大线程池数量
keepAliveTime:等待时间
timeUnit:等待时间的单位。
workQueue:阻塞队列
ThreadFactory:线程工厂,创建线程的。
handler:拒绝策略
拒绝策略
拒绝策略是指,当线程池添加任务失败的时候执行的策略。
默认的拒绝策略有4种拒绝策略。
AbrotPolicy:这个策略会抛出一个RejectExecutionExceptin异常。
CallRunsPolicy:这个策略会直接开一个线程将任务执行。
DiscardOldestPolicy:这个策略会移除最旧的那一个任务。
DiscardPolicy:这个策略什么都不做,直接抛弃该任务。
如何使用线程池
Java中一般有两种方式创建线程池,一种是使用Executors提供的的工厂方法,还有一种是手动创建ThreadPoolExecutor对象。推荐使用手动创建的方法,因为手动创建可以使用ThreadFactory给线程添加名字,这样有个好处,就是当出现了线上bug,需要查线程池中的线程时,可以快速定位出现问题的线程。并且自定义线程可控性更强。
手动创建线程池
package com.xymxyg.threadpool;
import java.util.concurrent.*;
/**
* @author guangsheng.tang
*/
public class CreateThreadPool {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 0, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(16), new ThreadPoolExecutor.DiscardPolicy());
executor.execute(() -> {
System.out.println("lalal");
System.out.println("lambdaTest");
});
ThreadPoolExecutor newExecutor = new ThreadPoolExecutor(5, 5,
0, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(16), r -> {
Thread thread = new Thread(r);
thread.setName("我的线程");
return thread;
});
newExecutor.execute(() -> {
System.out.println("啦啦啦");
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
System.out.println("线程被中断");
}
});
//使用线程池的时候,使用shutdown或者shutdownNow关闭线程池。
executor.shutdown();
newExecutor.shutdownNow();
}
}
使用Executors工厂类创建线程池
Executors是Java提供的工厂类,提供了几个静态工厂方法,可以快速的创建线程池。这几个静态工厂方法都是通过创建ThreadPoolExecutor对象的。
newFixedThreadPool(2);
这个是固定核心线程池数的线程池。查看Executors的源码可以看出实现方法。关于阻塞队列,将在另一篇文章里介绍。
new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>())
这个线程池使用的LinkedBlockingQueue这个阻塞队列,这个队列是有限的,长度为MAX_INT_VALUE;
Executors.newCachedThreadPool();
new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
这个线程池使用的是SynchronousQueue,这个队列的特点是队列大小为0,一个插入,必须对应一个取出。这个线程池的显著特点是核心大小为0,当来了任务时,它会创建一个线程去执行,并且它会缓存线程,在60s内没有任务来到,则会被销毁。
Executors.newScheduledThreadPool(2);
这个线程池值得注意,他返回一个ScheduledExecutorService对象,可以根据时间需要对线程进行调度。使用这个线程池就不用submit或者exeuce方法了,应该使schedule,scheduleAtFixedRate或者scheduleWithFixedDelay方法。schedule方法不会立即执行这个任务,而是在延迟一定的时候后进行执行。shceduleAtFixedRate和scheduleWithFixedDelay方法会循环调度任务,
Executors.newSingleThreadScheduledExecutor();
这个线程池没有什么好说的,就是上一个线程池的单线程版本。
线程池的关闭
使用shutdown()或者shutdownNow()方法进行关闭。shutdown方法会将线程池的状态设置为shutdown状态,不允许提交新的任务,但是会等待线程池中已经提交的线程执行完毕。
shutdownNow方法设置的状态是STOP状态,不会等待线程池中的线程执行结束,它会尝试中断线程池中运行的线程,如果该线程不能响应中断,那么它可能永远也不会停止。shutdownNow方法会返回没有执行的线程。
线程池的工作原理
当提交一个Runable的时候(调用execute或者submit方法),线程池会把该Runnable封装为一个Task,如果此时线程池中的线程小于核心线程,则会创建一个新的线程进行执行,否则尝试插入到阻塞队列中,如果阻塞队列满,则会尝试创建额外线程(当线程数量小于最大线程数量的时候),如果失败,则会执行拒绝策略。
Runnable和Callable的区别
Runnable和Callable都是线程池中的执行单元,它们都可以提交到线程池中进行执行,但是他们有一点儿区别,Runnable是没有返回值的, Callable是有返回值的。并且提交Runnable是用execute方法,提交Callable是使用submit方法。