一、为什么要使用线程池
降低资源消耗;
提高响应速度;
提高线程的可管理性。
二、Executor框架
将任务和线程本身解耦。Executor将任务提交与执行解耦开来,还提供了对生命周期的支持和监视机制。
1.Executor
public interface Executor {
void execute(Runnable command);
}
提供了execute()方法来执行任务。
2.ExecutorService
public interface ExecutorService extends Executor {
void execute(Runnable command);
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
}
ExecutorService是一个比Executor使用更广泛的子类接口。
三、ThreadPoolExecutor的构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler){...}
(1)corePoolSize :线程池中的核心线程数
当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。
(2)maximumPoolSize :线程池中允许的最大线程数
如果当前阻塞队列满了,判断是否达到最大线程数,若没有,则创建新的线程执行任务,否则进行拒绝策略。但如果使用了无界的任务队列这个参数就没用了。
(3)keepAliveTime :线程空闲时的存活时间
当线程没有任务执行时,继续存活的时间;默认情况下,该参数只在线程数大于corePoolSize时才有用。
(4)unit :keepAliveTime的单位
(5)workQueue :用来保存等待被执行的任务的阻塞队列
任务必须实现Runable接口,一共4有个阻塞队列:
ArrayBlockingQueue:基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。
LinkedBlockingQuene:基于链表结构的无界阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene;
SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene;
priorityBlockingQuene:具有优先级的无界阻塞队列;
(6)threadFactory :创建线程的工厂
可以选择DefaultThreadFactory,将创建一个同线程组且默认优先级的线程,也可以选择PrivilegedThreadFactory,使用访问权限创建一个权限控制的线程。但默认采用DefaultThreadFactory,当然也可以创建自定义线程工厂给每个新建的线程设置一个具有识别度的线程名。
(7)handler
线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:
AbortPolicy:直接抛出RejectedExecutionException异常,默认策略;
CallerRunsPolicy:如果发现线程池还在运行,就直接运行这个线程,即用调用者所在的线程来执行任务;
DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
DiscardPolicy:直接丢弃任务;
当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。
四、线程池的基本操作
1、创建线程池
Executors类为创建各种线程池提供了一系列静态工厂方法。
如:
ExecutorService threadPool = Executors.newCachedThreadPool();
(1) newFixedThreadPool方法 :创建固定大小的线程池
适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景,即负载比较重的服务器
FixedThreadPool方法源码如下:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
可以看出,其实是初始化了一个指定线程数的线程池,其中corePoolSize == maximumPoolSize,使用LinkedBlockingQuene作为阻塞队列,如果线程池没有可执行任务时,也不会释放线程。
(2)newCachedThreadPool() 方法 :缓存线程池,线程池的数量不固定,可以根据需求自动的更改数量。
大小无界的线程池,适用于执行很多的短期异步任务的小程序,或是负载较轻的服务器。
CachedThreadPool方法源码如下
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
分析:
初始化一个可以缓存线程的线程池,默认缓存60s,线程池的线程数可达到Integer.MAX_VALUE,即2147483647,内部使用不存储元素的SynchronousQueue作为阻塞队列;
newCachedThreadPool在没有任务执行时,当线程的空闲时间超过keepAliveTime,会自动释放线程资源,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销;
(3)newSingleThreadExecutor() 方法 : 创建单个线程池。线程池中只有一个线程
适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景。
SingleThreadExecutor方法源码如下
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
分析:
初始化的线程池中只有一个线程,如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的线程可以保证所提交任务的顺序执行,内部使用LinkedBlockingQueue作为阻塞队列。
(4)newScheduledThreadPool() 方法: 创建固定大小的线程,可以延迟或定时的执行任务。
适用于需要多个后台线程执行周期任务,同时为了满足资源 管理的需求而需要限制后台线程的数量的应用场景
newScheduledThreadPool方法源码如下:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
ScheduledThreadPoolExecutor源码:
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
继承了ThreadPoolExecutor的构造方法
分析:初始化的线程池可以在指定的时间内周期性的执行所提交的任务,在实际的业务场景中可以使用该线程池定期的同步数据。其实最底层还是通过ThreadPoolExecutor类实现的。
2、提交任务
(1)execute()方法 :用于提交不需要返回值的任务,
无法判断任务是否被线程池执行成功,线程池在执行excute方法时,会出现以下几种情况:
① 如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(需要获得全局锁);
②如果运行的线程等于或多于corePoolSize ,则将任务加入BlockingQueue;
③如果无法将任务加入BlockingQueue(队列已满并且正在运行的线程数量小于 maximumPoolSize),则创建新的线程来处理任务(需要获得全局锁)
④如果创建新线程将使当前运行的线程超出maxiumPoolSize(队列已满并且正在运行的线程数量大于或等于 maximumPoolSize),任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法(线程池会抛出异常,告诉调用者”我不能再接受任务了”);
(2)submit()方法 :用于提交需要返回值的任务
线程池会返回一个future类型的对象,通过这个 future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,但get()方 法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线 程一段时间后立即返回,这时候有可能任务没有执行完。
3、关闭线程池
(1)shutdown():将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
(2)shutdownNow():首先将线程池的状态设置成 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。
(3)可以调用isShutdown()来判断线程池是否关闭。
案例:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorsTest {
/**
* @param args
*/
public static void main(String[] args) {
线程数
int num = 10;
//CountDownLatch是一个同步辅助类也可以使用AtomicInteger替代
CountDownLatch doneSignal = new CountDownLatch(num);
ExecutorService pool = Executors.newFixedThreadPool(num);
for(int i=0;i<num;i++)
//在未来某个时间执行给定的命令
pool.execute(new WorkerRunnable(doneSignal, i));
try {
doneSignal.await();
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
//关闭线程池
pool.shutdown();
}
//子线程执行完毕,可以开始后续任务处理了
System.out.println("所有任务执行完毕");
}
}
class WorkerRunnable implements Runnable {
private final CountDownLatch doneSignal;
private final int i;
WorkerRunnable(CountDownLatch doneSignal, int i) {
this.doneSignal = doneSignal;
this.i = i;
}
public void run() {
//子线程执行任务
try{
doWork(i);
} catch (Exception e) {
e.printStackTrace();
}
//任务执行完毕递减锁存器的计数
doneSignal.countDown();
}
void doWork(int i) {
System.out.println("这是第"+(i+1)+"个任务");
}
}