1.线程池的优点
Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或者并发执行任务的程序都可以使用线程池。使用线程池的优点如下:
(1)降低资源消耗
通过重复利用已创建的线程降低线程创建与消耗带来的损耗
(2)提高响应速度
当任务到达时,无须等待线程创建就可以立即执行。
(3)提高线程的可管理性
使用线程池可以统一进行线程分配、调度与调控。
2.线程池的继承关系
ExecutorService(普通调度池核心接口)
submit(Callable|Runnable):Future<T>
ScheduledExecutorService(定时调度池核心接口)
schedule(Runnable|Callable command,long delay,TimeUnit unit):延迟delay个时间单位后开始执行。
scheduledAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit):延迟initialDelay个时间单位后开始执行,并且每隔period个时间单位就执行一次。
3.线程池执行流程(*****)
第一步:判断核心线程池是否已满,如果未满,创建一个新的线程来执行任务。如果已满,判断是否有空闲线程,有的话,将任务分配给空闲线程,否则,执行步骤2.(创建线程需要获得全局锁)
第二步:判断工作队列(阻塞队列BlockingQueue)是否已满。如果工作队列未满,将任务存储在工作队列中等待空闲线程调度。如果工作队列已满,执行步骤3.
第三步:判断当前线程池的线程数量是否已达到最大值,如果没有达到最大值,则创建新的线程来执行任务(创建线程需要获取全局锁)。否则,执行步骤4.
第四步:调用饱和策略来处理此任务。
4.线程池的使用
4.1 手工创建线程池(*****)
通过new一个ThreadPoolExecutor()就可以实现自定义线程池。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数说明:
1)corePoolSize(核心线程池大小):当提交一个任务到线程池时,只要核心线程池未达到corePoolSize,创建新线程来执行任务。调用preStartAllCoreThreads(),线程池会提前创建并启动所有核心线程。
2)BlockingQueue<Runnable> workQueue(任务队列):保存等待执行任务的阻塞队列。
阻塞队列:
-ArrayBlockingQueue:基于数组有界阻塞队列,按照FIFO原则对元素进行排序。
-LinkedBlockingQueue:基于链表的阻塞队列,按照FIFO排列元素。吞吐量要高于ArrayBlockingQueue。内置线程池newFixedThreadPool-固定大小线程池就采用此队列。
-SynchronousQueue:一个不存储元素的阻塞队列(无界队列)。每个插入操作需要等待另一个线程的移除操作,否则插入操作一直处于阻塞状态。吞吐量通常高于LinkedBlockingQueue。内置线程newCachedThreadPool-缓存线程池就采用此队列。
-PriorityBlockingQueue:具有优先级的阻塞队列。
3)maximumPoolSize(线程池最大数量)
4)keepAliveTime(线程保持活动的时间):线程池的工作线程空闲后,保持存活的时间。如果任务比较多并且每个任务执行的时间比较短,那么可以调大此参数来提高线程利用率。
5)RejectedExecutionHandler(饱和策略)
-AbortPolicy:无法处理新任务抛出异常(JDK默认采用此策略)。
-CallerRunsPolicy:使用调用者所在线程来处理任务。
-DiscardOldestPolicy:丢弃队列中最近的一个任务,并执行当前任务。
-DiscardPolicy:不处理任务,丢弃任务,也不报异常。
6)TimeUnit unit:时间单位
4.2 向线程池提交任务
使用两个方法向线程池提交任务,分别为execute()和submit()。
execute()用于提交不需要返回值的方法,所以无法判断线程池是否执行任务成功。
创建无返回值的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
class MyThread implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"、"+i);
}
}
}
public class Demo {
public static void main(String[] args) {
MyThread myThread=new MyThread();
ExecutorService executorService=new ThreadPoolExecutor(3,5,2000,TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>());
for(int i=0;i<5;i++){
executorService.execute(myThread);
}
}
}
//这三个线程很快就执行完,不会达到最大线程池数量。
submit()用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判定任务是否执行成功,并且可以通过future的get()来获取返回值,get()会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法会阻塞当前线程一段时间之后立即返回,此时有可能任务没有执行完。
创建有返回值的线程池
import java.util.concurrent.*;
class MyThread implements Callable {
@Override
public String call() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"、"+i);
}
return Thread.currentThread().getName()+"执行完毕";
}
}
public class Demo {
public static void main(String[] args) {
MyThread myThread=new MyThread();
ExecutorService executorService=new ThreadPoolExecutor(3,5,2000,TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>());
//调用future的get(),会阻塞其它线程,直到取得当前线程执行完毕后的返回值。
//FutureTask中的任务只会被执行一次。
for(int i=0;i<5;i++){
Future future=executorService.submit(myThread);
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
executorService.shutdown();//关闭线程池
}
}
4.3 关闭线程池
通过调用线程池的shutdown或shutdownNow方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt()来中断线程,所以无法响应中断的任务可能永远无法终止。但是它们有区别shutdown():安全关闭 shutdownNow():立即关闭,具体区别如下:
shutdownNow首先将线程池的状态设置为STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。
shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
只要调用了这两个关闭方法中的任意一个,isShutdown方法就会返回true。当所有的任务都关闭之后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。至于调用哪个方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown方法来关闭线程池。如果任务不一定要执行完,则可以调用shutdownNow()。
5.内置四大线程池
5.1 固定大小线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
应用场景:固定大小线程池适用于为了满足资源管理需求,而需要限制当前线程数量的应用场合,适用于负载较重的服务器。
5.2 单线程池newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
应用场景:适用于需要保证顺序地执行各个任务,并且在任意时间不会有多个线程。
5.3 缓存线程池CachedThreadPool
根据需要创建新线程。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
当提交任务速度快于执行任务速度,缓存线程池会不断创建新线程;如果提交任务速度大于执行任务速度,只创建一个线程。
适用场景:大小无界线程池,适用于执行很多短期异步小程序,或者负载较轻的服务器。
import java.util.concurrent.*;
class MyThread implements Callable {
@Override
public String call() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"、"+i);
}
return Thread.currentThread().getName()+"执行完毕";
}
}
public class Demo {
public static void main(String[] args) {
MyThread myThread=new MyThread();
ExecutorService executorService=Executors.newCachedThreadPool();
for(int i=0;i<5;i++){
executorService.submit(myThread);
}
executorService.shutdown();
}
}
使用该线程池会创建5个线程。
5.4 定时调度池newScheduledThreadPool(int corePoolSize)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
应用场景:需要定时执行任务的应用场景。