线程池在juc包下。
线程池是用来实现多线程的,那已经有Runnable和Callable实现多线程了,为什么还要线程池呢?
因为使用线程池有以下三个优点:
- 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁带来的消耗(当一个线程跑完一个任务之后,不会被销毁,会再次给这个线程分配一个新任务)
- 提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行(因为线程池中一直有空闲的线程在等待新任务的到来,等任务来了之后直接分配线程执行,不需要等待)
- 提高线程的可管理性:使用线程池可以统一进行线程分配、调度和监控
线程池的继承关系:
ScheduledExecutorService----schedule(Callable<V> callable, long delay, TimeUnit unit):是在delay时间后执行unit这么长时间,并且每隔delay时间后执行unit这么长时间
1. 线程池的主要处理流程
当一个Runnable或Callable对象到达线程池时,执行策略如下:
- 首先判断核心线程池中的线程是否都在执行任务,如果是,再次查看核心线程池是否已满,如果未满,创建新的线程执行任务。如果满了,查看核心线程池是否有空闲线程,若要空闲线程,则将任务直接分配给空闲线程执行。否则执行第二步。
- 判断阻塞队列(BlockingQueue)是否已满,如果工作队列没有满,将任务存储到阻塞队列中等待核心线程池的调度;否则,若阻塞队列已满,则执行第三步。
- 判断当前线程池中的线程数是否已达到最大值maximumSize,若已达到最大值,将任务交给饱和策略处理;否则,继续创建新线程执行此任务。
2. 如何使用线程池
2.1 手工创建线程池
通过创建ThreadPoolExecutor来创建线程池
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
- corePoolSize(核心池的大小):当提交任务到线程池时,线程池会创建一个新的线程来执行任务,即使核心池中有其他空闲线程也会创建新线程,一直到线程数达到了核心池的大小为止。
调用prestartAllCoreThreads()线程池会提前创建并启动所有核心线程,这样当新任务来的时候,就不会创建新线程了,而是用空闲线程(因为线程数已经达到了核心池的大小) - workQueue(工作队列):用于保存等待执行任务的阻塞队列。
可以选择以下几个阻塞队列:
(1)ArrayBlockingQueue:基于数组结构的有界阻塞队列,此队列按照FIFO原则对元素进行排序。
(2)LinkedBlockingQueue:基于链表结构的阻塞队列,按照FIFO原则对元素进行排序,吞吐量高于ArrayBlockingQueue
Exectors.newFixedThreadPool()—内置的固定大小的线程池,Executors的这个方法采用的就是LinkedBlockingQueue队列
(3)SynchronousQueue:一个不存储元素的无界阻塞队列。每个插入操作必须等到另一个线程调用移除操作之后,否则插入操作一直处于阻塞状态,通常吞吐量比LinkedBlockingQueue还要高。
Executors.newCachedThreadPool()----内置缓存线程池,采用SynchronousQueue。
(4)PriorityBlockingQueue:具有优先级的无界阻塞队列。 - maximumPoolSize(线程池最大的线程数量):线程池允许创建的最大线程数。如果队列已满并且已创建的线程数小于此参数,则线程池会再创建新的线程执行任务。否则,调用饱和策略处理。如果采用无界队列,此参数无意义(因为只有队列满了之后,才会用到这个参数,无界队列是永远都不会满的)
- keepAliveTime(线程保持活动时间):线程池的工作线程空闲后,保持存活的时间。如果空闲线程超过这个时间还没有任务的话,就被销毁了。若任务很多,并且每个任务执行的时间较短,可以调大此参数来提高线程利用率。
- TimeUnit (线程活动保持时间的单位)
- RejectedExecutionHandler (饱和策略):当队列和线程池都满了,说明线程池处于饱和状态。此时采用饱和策略来处理任务,默认采用AbortPolicy。JDK一个内置4个饱和策略:
a. AbortPolicy,表示无法处理新任务抛出异常,JDK 默认采用此策略
b. CallerRunsPolicy,等待调用者线程空闲后运行任务(一般都是主线程调用饱和策略,等主线程空闲后,调用主线程处理该任务;如果是在A线程中创建线程池,则线程A是调用者)。
c. DiscardOldestPolicy,丢弃阻塞队列中最近的一个任务,并执行当前任务。
d. DiscardPolicy,不处理,直接将新任务丢弃,也不报错。
例:手工创建一个线程池
- 调用executorService.execute()方法,传入Runnable型参数
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 Test {
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);
}
executorService.shutdown();
}
}
- 调用executorService.submit()方法,传入Callable型参数
import java.util.concurrent.*;
class MyThread implements Callable<String> {
@Override
public String call() {
for(int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName()+"、"+i);
}
return Thread.currentThread().getName()+"执行结束";
}
}
public class Test {
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++){
Future future = executorService.submit(myThread);
// try {
// System.out.println(future.get());
// } catch (InterruptedException e) {
// e.printStackTrace();
// } catch (ExecutionException e) {
// e.printStackTrace();
// }
}
executorService.shutdown();
}
}
如果我们屏蔽掉future.get()方法,发现程序的输出结果是三个线程交替执行的。
如果将future.get()–获得返回值 方法放开,会发现输出结果是这样的:
从结果就可以推断出,Future.get()会阻塞其他线程,一直等到当前Callable线程执行完毕拿到返回值为止。
3. JDK内置四大线程池
普通调度池
- 创建无大小限制的线程池:Executors.newCachedThreadPool()
适用于很多短期任务的小程序,负载较轻的服务器。 - 创建固定大小线程池:Executors.newFixedThreadPool(int nThreads)
适用于为了满足资源管理的需求而需要限制当前线程数量的应用场合,适用于负载比较重的服务器。 - 单线程池:Executors.newSingleThreadPool()
适用于需要保证顺序的执行各个任务,并且在任意时间点,不会有多个线程活动的场景。
定时调度池
ScheduledExecutorService.schedule(Callable<V> callable,long delay, TimeUnit unit);
-----表示延迟多长时间执行一次
ScheduledExecutorService.scheduleAtFixedRate(Runnable command,long initiaDelay,long period,TimeUnit unit);
----表示延迟多长时间,然后每个period时间作为一个周期执行一次