java线程池相关
一,四种常见的线程池类型
1.SingleThreadExecutor,单线程化的线程池
//使用Executors 工具类来创建一个单线程的线程池
//单线程线程池,可以保证线程按照顺序执行
ExecutorService singleThread = Executors.newSingleThreadExecutor();
// ExecutorService是Java提供的用于管理线程池的接口,用于管理线程数量和重用线程
//调用execute方法执行任务,方法的参数是Runnable类型
//把需要使用的线程来执行任务,提交给线程池,由线程池来给任务分配线程资源
singleThread.execute(() ->{
System.out.println("first");
});
singleThread.execute(() ->{
System.out.println("second");
});
singleThread.execute(() ->{
System.out.println("third");
});
运行结果:
first
second
third
2.FixedThreadPool,可重用固定个数的线程池
//创建定长线程池,方法参数就是线程池的线程数量 ,是可重用固定个数的线程池
//该线程池不是创建里面有5个线程,而是随着任务的增加而增加
//来了一个线程之后,线程池会创建一个线程,如果后续来了新的线程,并且目前没有空闲线程
//线程池会新创建新的线程来执行新任务,直到新线程池里的线程数量达到5之后,则不会创建新线程
//应用场景: 需要使用多个线程来同时处理任务,并且线程数量不能太多,需要控制
ExecutorService fixThreadPool = Executors.newFixedThreadPool(5);
fixThreadPool.execute(() -> {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("111");
});
fixThreadPool.execute(() -> {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("222");
});
fixThreadPool.execute(() -> {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("333");
});
输出结果:
222
111
333
线程执行的顺序不是固定的
for(int i=0; i<10; i++){
fixThreadPool.execute(()->{
System.out.println("正在执行" + Thread.currentThread().getName());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
输出结果:
正在执行pool-2-thread-1
正在执行pool-2-thread-3
正在执行pool-2-thread-2
正在执行pool-2-thread-4
正在执行pool-2-thread-5
正在执行pool-2-thread-3
正在执行pool-2-thread-5
正在执行pool-2-thread-1
正在执行pool-2-thread-4
正在执行pool-2-thread-2
线程的数量达到设置的最大数量之后,不会再创建新的线程
3.CachedThreadPool 可以缓存的线程池
//创建一个可以缓存的线程池,不需要参数
//线程数量随着任务数量的变化而变化,任务多则会自动创建线程
//任务少,则会自动回收线程
//系统任务数量不太确定,忽高忽低,任务执行速度较快的应用场景可以使用该线程
//任务数量特别多的项目里不适合使用这种线程池,因为可能导致线程过多,程序挂掉
ExecutorService cachedThread = Executors.newCachedThreadPool();
ExecutorService cachedThread = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
cachedThread.execute(() -> {
System.out.println("正在执行" + Thread.currentThread().getName());
});
}
cachedThread.shutdown();
输出结果:
正在执行pool-2-thread-1
正在执行pool-2-thread-2
正在执行pool-2-thread-3
正在执行pool-2-thread-3
正在执行pool-2-thread-2
正在执行pool-2-thread-1
正在执行pool-2-thread-2
正在执行pool-2-thread-4
正在执行pool-2-thread-5
正在执行pool-2-thread-6
缓存线程池会重复利用之间已经创建的线程
给线程加上睡眠时间,
ExecutorService cachedThread = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
cachedThread.execute(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在执行" + Thread.currentThread().getName());
});
}
运行结果:
正在执行pool-2-thread-3
正在执行pool-2-thread-1
正在执行pool-2-thread-4
正在执行pool-2-thread-2
正在执行pool-2-thread-6
正在执行pool-2-thread-9
正在执行pool-2-thread-10
正在执行pool-2-thread-5
正在执行pool-2-thread-7
正在执行pool-2-thread-8
因为线程的睡眠时间长会导致缓存池中的线程被回收,所以每次都会创建新的线程
查看源码可以发现,缓存线程中的最大线程数是int整型的最大值
4.ScheduledThreadPool 周期性线程池
//创建一个周期性线程池
ScheduledExecutorService schedule = Executors.newScheduledThreadPool(1);
System.out.println("schedule start");
//延迟10秒,执行线程
schedule.schedule(() -> {
System.out.println("aaaaaa");
}, 10, TimeUnit.SECONDS);
//周期性执行,可以做一些需要周期性重复执行的任务,比如定时通知,定时清理等任务
schedule.scheduleAtFixedRate(() -> {
System.out.println("定时周期执行");
}, 3, 10, TimeUnit.SECONDS);//延迟3秒后,每10秒执行一次
二,自定义线程池
//第一个参数为核心线程数,第二个是最大线程数,第三个是等待时间
//第四个是时间单位,第五个是等待队列对象,第六个参数是该线程池的拒绝策略
ExecutorService es = new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(100), new ThreadPoolExecutor.CallerRunsPolicy());
es.execute(() -> {
System.out.println("自己创建的线程池");
});
源码
三,停止线程池
线程池在程序运行过程中不会自动关闭,需要我们添加代码手动关闭
1.调用shutdown方法停止线程池
shutdown方法会等待线程中的任务执行完毕再关闭线程池
singleThread.execute(() -> {
System.out.println("first");
});
singleThread.execute(() -> {
System.out.println("second");
});
singleThread.execute(() -> {
System.out.println("third");
});
运行结果:
first
second
third
2.调用shutdownNow方法停止线程池
shutdownNow不会等待线程结束,而是立即关闭线程池
运行结果:
first
四,execute()方法和submit()方法的区别
1.execute()方法只用于任务执行,无法获取结果
//execute方法用于执行任务,但是不关注任务的结果
single.execute(()->{
System.out.println("execute运行了");
});
运行结果:execute运行了
2.submit()方法不仅可以执行任务,也可以获取执行结果
public class ThreadPoolTest2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService single = Executors.newSingleThreadExecutor();
//submit方法可以获取到任务执行的结果
//接收Callable接口的实现类对象
Future<String> fu = single.submit(new MyCallable());
//通过future的get方法,来获取任务的结果
String result = fu.get();
System.out.println(result);
}
}
//重写Callable接口的run方法
class MyCallable implements Callable<String>{
@Override
public String call() throws Exception {
return "运行结果";
}
}
输出结果:运行结果
//submit方法也是可以使用lambda表达式的,加上返回值即可
single.submit(() ->{
System.out.println("程序又运行了");
return "我是程序运行结果";
});
五、在项目中部署线程池
在springboot中部署线程池,需要创建bean
首先,创建一个类,通过方法来创建bean
//这是一个用来管理bean的类,需要使用configuration注解
@Configuration
public class BeanConfig {
//springboot中,使用方法来创建bean,该方法的返回值类型,就是我们需要创建的bean类型
@Bean
public ExecutorService threadPool(){
int coreSize = 5;
int maxSize = 10;
int time = 10;
ExecutorService es = new ThreadPoolExecutor(coreSize, maxSize,time,
TimeUnit.SECONDS, new LinkedBlockingDeque<>(100));
return es;
}
}
在service层或者contrller层中注入
@Resource(name = "threadPool")
private ExecutorService executorService;
如果我们创建了多个bean,多个线程池,那么可以通过name来区分这些线程池,name就是我们在bean中定义的创建线程池的方法名
之后,我们就可以通过对象名来调用线程池了
//使用线程池来执行任务
executorService.execute(() ->{
asyncFetch(body, url, novel.getId());
});
参考博文:https://blog.csdn.net/hnd978142833/article/details/80253784