JUC线程进阶篇09:线程池
标签: 多线程
了解线程池
为什么使用线程池
线程池提供了一个线程队列,队列中保存着所有等待状态的线程。合理利用线程池能够带来三个好处:
1. 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2. 提高响应速度:当任务到达时,任务可以不需要的等到线程创建就能立即执行。
3. 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
线程池工作流程
当提交一个新任务到线程池时,线程池的处理流程如下:
- 首先线程池判断基本线程池是否已满?没满,创建一个工作线程来执行任务。满了,则进入2。
- 其次线程池判断工作队列是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进3。
- 最后线程池判断整个线程池是否已满?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务。
线程池体系结构
java中的线程池是通过Executor
框架实现的,Executor
框架包括类:Executor
,Executors
,ExecutorService
,ThreadPoolExecutor
,Callable
和Future
、FutureTask
的使用等。
Executor主要结构
Java里面线程池的顶级接口是Executor
,但是严格意义上讲Executor
并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService
。
java.util.concurrent.Executor:负责线程的使用与调度的根接口
|-- ExecutorService子接口:增加Executor的行为,是Executor实现类的最直接接口
|-- ThreadPoolExecutor:线程池实现类
|-- ScheduledExecutorService子接口:负责线程池的调度
|-- ScheduledThreadPoolExecutor实现类:继承了ThreadPoolExecutor类,实现了ScheduledExecutorService接口
ThreadPoolExecutor创建线程池
我们可以通过ThreadPoolExecutor
来创建一个线程池。
new ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
这个构造方法有7个参数:
corePoolSize
:池中所保存的线程数,包括空闲线程。maximumPoolSize
-池中允许的最大线程数。keepAliveTime
– 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。unit
– keepAliveTime 参数的时间单位。workQueue
– 执行前用于保持任务的队列。此队列仅保持由execute方法提交的 Runnable任务。threadFactory
– 执行程序创建新线程时使用的工厂。handler
– 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
在JDK API文档中,有如此一段话:
强烈建议程序员使用较为方便的Executors工厂方法Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)(固定大小线程池)Executors.newSingleThreadExecutor()(单个后台线程)它们均为大多数使用场景预定义了设置。
Executors工具类创建线程池
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors
工具类中提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService
接口:
ExectorService newFixedThreadPool()
:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。ExectorService newCachedThreadPool()
:缓存线程池,线程池的数量不固定,可以根据需求自动的更改数量
3.ExectorService newSingleThreadExecutor()
:创建单个线程池,这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
ScheduledExectorService newScheduledThreadPool()
:创建固定大小的线程池,可以延迟或定时地执行任务
线程池的使用
线程池的使用分为3步:
1. 创建线程池
2. 为线程池中的线程分配任务
3. 关闭线程池
重要方法
在ThreadPoolExecutor类中有几个非常重要的方法:
execute() submit() shutdown() shutdownNow()
execute()
方法实际上是Executor
中声明的方法,在ThreadPoolExecutor
进行了具体的实现,这个方法是ThreadPoolExecutor
的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。submit()
方法是在ExecutorService
中声明的方法,在AbstractExecutorService
就已经有了具体的实现,在ThreadPoolExecutor
中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()
方法不同,它能够返回任务执行的结果,去看submit()
方法的实现,会发现它实际上还是调用的execute()
方法,只不过它利用了Future
对象的的future.get()
方法来获取任务执行结果。shutdown()
和shutdownNow()
是用来关闭线程池的,一个较平稳,一个立即关闭。
代码演示
使用Runnable方法创建线程:
public class TestThreadPool {
public static void main(String[] args) {
// 1.创建线程池
ExecutorService pool = Executors.newFixedThreadPool(5);//创建了5个线程的线程池
ThreadPoolDemo td = new ThreadPoolDemo(); //创建一个线程任务
// 2.为线程池中的线程分配任务
for (int i = 0; i < 10; i++) {
pool.submit(td);
}
// 3.关闭线程池
pool.shutdown();
}
}
class ThreadPoolDemo implements Runnable{
private int i = 0;
@Override
public void run() {
while (i<= 5) {
System.out.println(Thread.currentThread().getName()+":"+i++);
}
}
}
使用Callable创建线程的方式,有返回值,使用Future获取:
public class TestThreadPool {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 1.创建线程池
ExecutorService pool = Executors.newFixedThreadPool(5);//创建了5个线程的线程池
List<Future<Integer>> list = new ArrayList<Future<Integer>>();//Future的队列,用于获取多个Future对象
for (int i = 0; i < 10; i++) {
Future<Integer> future = pool.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum +=i;
}
return sum;
}
});
list.add(future);
}
// 3.关闭线程池
pool.shutdown();
for (Future<Integer> future: list) {
System.out.println(future.get());
}
}
}
线程调度
在线程池中提到了一个ScheduledExecutorService这一个类就是用来实现线程的调度的。
public class TestScheduledThread {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 使用newScheduledThreadPool创建五个可调度的线程的线程池
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
for (int i = 0; i < 5; i++) {
// schedule有三个参数:分配的任务、延时时间、单位
ScheduledFuture<Integer> result = pool.schedule(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int num = new Random().nextInt(100);//产生随机数
System.out.println(Thread.currentThread().getName()+":"+num);
return num;
}
}, 3, TimeUnit.SECONDS);
System.out.println(result.get());
}
pool.shutdown();
}
}