线程池与线程调度
线程池的好处
- 避免频繁地创建和销毁线程
- 创建了的线程,用完后可以重复利用
- 可以类比数据库连接池理解
直接看具体使用代码
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
/**
* 一、线程池 :提供了一个线程队列,队列中保存着所有等待状态的线程
* 避免了创建和销毁线程的额外开销,提高了响应速度
* 二、线程池体系结构:
* java.util.concurrent.Executor :负责线程的使用与调度的根接口
* |-- ExecutorService 子接口: 线程池的主要接口
* |-- ThreadPoolExecutor : 线程池的实现类
* |-- ScheduledExecutorService 子接口 : 负责线程的调度
* |-- ScheduledThreadPoolExecutor : 继承了线程池的实现类ThreadPoolExecutor,
实现了ScheduledExecutorService接口
* 三、工具类:Executors
* ExecutorService newFixedThreadPool():创建固定大小的线程池
* ExecutorService newCachedThreadPool():缓存线程池,线程池数量不固定,可根据需求自动更改数量
* ExecutorService newSingleThreadExecutor():创建单个线程池。线程池中只有一个线程
*
* ScheduledExecutorService newScheduledThreadPool():创建固定大小的线程,延迟或定时地执行任务
*
*/
public class TestThreadPool {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadPoolDemo tpd = new ThreadPoolDemo();
//1.创建线程池
ExecutorService pool = Executors.newFixedThreadPool(5);
List<Future> list = new ArrayList<>();
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);
}
for(Future future:list)
{
System.out.println(future.get());
}
//2.为线程池中的线程分配任务
// for(int i = 0;i<100;i++)
// {
// pool.submit(tpd);
// }
//3.关闭线程池
pool.shutdown();
}
}
class ThreadPoolDemo implements Runnable{
private int i =0;
@Override
public void run() {
while(i<=100){
System.out.println(Thread.currentThread().getName()+":"+ i++);
}
}
}
代码逻辑:
- 创建了一个核心线程池数为5的线程池
- 分别使用
submit
方法添加线程,有的添加Runnable
,有的添加Callable
,发现Callable
有返回结果可以获取
注意点:
- 使用线程池启动的不同方式
- submit和execute都可以添加线程任务,但submit有返回值,execute没有
- submit返回的是一个future ,可以通过这个future取到线程执行的结果或者异常信息
- 使用submit提交子任务,一定要获取返回值Future,通过get方法获取可能出现的异常,并且可以进行捕获(推荐)
- 使用execute执行子任务,异常可以被抛出,但是主线程不能捕获子任务线程中的异常
- 使用submit提交子任务,只是提交,不获取返回值future,异常会被封装在子线程内部,不会抛出,主线程也无法捕获
- shutdown 已提交的任务继续执行,不接受新任务提交
- 在实际使用中,最好使用
ThreadPoolExecutor
来创建线程池 - 一方面是由于
jdk
中Executor
框架虽然提供了如newFixedThreadPool()
、newSingleThreadExecutor()
、newCachedThreadPool()
等创建线程池的方法,但都有其局限性,不够灵活;另外由于前面几种方法内部也是通过ThreadPoolExecutor
方式实现,使用ThreadPoolExecutor
有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险
线程调度
线程调度主要关注线程的执行方式和策略
看如下代码
import java.util.Random;
import java.util.concurrent.*;
public class TestScheduledThreadPool {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
for(int i =0;i<10;i++)
{
Future<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;
}
},1, TimeUnit.SECONDS);
}
pool.shutdown();
}
}
代码逻辑:
-
起一个线程池,依次往该池中添加线程(
Callable
),输出当前线程名+一个随机数 -
规定每次线程执行前都等待一秒时间
实际执行效果:
pool-1-thread-1:31
31
pool-1-thread-1:10
10
pool-1-thread-2:13
13
pool-1-thread-1:89
89
pool-1-thread-3:83
83
pool-1-thread-2:8
8
pool-1-thread-4:63
63
pool-1-thread-1:76
76
pool-1-thread-5:26
26
pool-1-thread-3:81
81
每打印一次都等待一秒
线程池的调度可以做到延迟执行,定时执行等功能