线程池使用实例
先写一个配置类
/*** 线程池配置*/
@Configuration
public class ThreadPoolConfig {//定义线程前缀public static final String NAME_PRE="test";/*** ExecutorService 这个对象就是线程池,可以点进去他的源码看看* @Bean,将getExecutor()方法返回的对象交给Spring管理*/@Beanpublic static ExecutorService getExecutor(){/***方法一*ExecutorBuilder e=new ExecutorBuilder();*e.setAllowCoreThreadTimeOut(false);**//**方法二直接Builder赋值**/return ExecutorBuilder.create().setCorePoolSize(8).setMaxPoolSize(16).setKeepAliveTime(60, TimeUnit.SECONDS).setHandler(new ThreadPoolExecutor.CallerRunsPolicy()).setWorkQueue(new LinkedBlockingDeque<>(2000)).setThreadFactory(ThreadFactoryBuilder.create().setNamePrefix(NAME_PRE).build()).setAllowCoreThreadTimeOut(false).build();}
}
- ExecutorService 这个对象是线程池
- @Bean,在项目启动时执行ExecutorService类的方法,返回一个对象交给spring管理。
- @Configuration,配置注解,在项目启动时,首先执行这个类的代码。
- 返回的对象第一种方法用set方法去赋值;第二种用Builder赋值。
参数内容为数字也通常为魔法值
方法1、可以放到配置文件中(yml配置)
threadpool:corepoolsize: 8maxPoolSize: 16# 队列大小dequesize: 2000# 线程前缀namepre: test-
方法2、设置静态常量
//定义线程前缀
public static final String NAME_PRE="test";.setThreadFactory(ThreadFactoryBuilder.create().setNamePrefix(NAME_PRE).build())
这样一个线程池就建好了
写好了怎么用呢?
由于ExecutorService是一个interface,且交给了spring管理,所以直接注入使用。 这里在Impl注入。
@Resource
private ExecutorService executorService;
写一个测试尝试一下:
Service中写接口
/**Service中写接口**/void test() throws InterruptedException;
Impl中重写方法(先模拟不用线程池)
使用@SentinelResource
注解 value指定资源的名称,blockHandler用于指定服务限流后的后续处理逻辑。
依赖:
<!-- 如果要使用@SentinelResource必须添加此依赖 -->
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-annotation-aspectj</artifactId><version>1.8.1</version>
</dependency>
/**Impl中重写方法**/@SentinelResource(value = "test",blockHandler="exceptionHandler")
@Override
public Boolean test() throws InterruptedException {/**模拟业务场景耗时**/Thread.sleep(100000);return true;
}
controller中调用接口
/**controller中调用接口**/@GetMapping("/open/test")
public Boolean test() throws InterruptedException {userService.test();return Boolean.TRUE;
}
我们可以看到,由于睡眠操作,访问接口一直显示加载状态
怎么用线程池?两个方法
方法一 注解方式(用的不多)
方法二 使用CompletableFuture
@Override
public void test() throws InterruptedException {log.info("进来了");CompletableFuture.runAsync(()->{try {log.info("进来了1111");/**模拟业务场景耗时**/Thread.sleep(100000);} catch (InterruptedException e) {e.printStackTrace();}},executorService);}
浏览器访问会立即打开
后台日志也表明已将任务交给线程池
多执行几次看看
我们发现,线程池到 test7 后就没有了,因为我们设置的核心线程数为8,这8个线程都在帮我们干活,再多的线程任务将放置到队列中。
等一小会....
发现刚刚被放入队列未执行的任务又被执行了,因为有线程执行完任务,就去队列中拿新任务去执行了。
使用多线程的三种形式
1、将任务交给线程池去做,至于成不成功、需不需要返回值,不关注。
例如新增用户的三个任务交给线程池,不关注有没有成功。当主线程将所有任务交给线程池后,主线程就认为新增用户这个任务完成了,去进行下一项任务。
===> 适用于更新操作,不关注返回值
2、将任务交给线程池去做,需要等待任务返回的结果。当主线程将任务交给线程池后,进入阻塞状态,直到获得所有的返回值。
例如将新增用户改为查询用户信息,我们知道查询一定是要返回结果的,所以主线程需要等待线程池内的任务执行完毕,他才能继续下一项任务,在这期间主线程一直处于阻塞状态。
===> 适用于多IO操作
,如:for循环查数据库、循环调用http接口查询、多次查询数据库操作
模拟一下多线程查询
@Override
public void test() throws InterruptedException {log.info("进来了");//QueryBo为构建的返回值QueryBO queryBO=new QueryBO();//获取开始时间long start = System.currentTimeMillis();//创建线程,将任务放到线程池CompletableFuture<Void> query1 = CompletableFuture.runAsync(() -> {Boolean aBoolean = null;try {aBoolean = query1();} catch (InterruptedException e) {e.printStackTrace();}queryBO.setQuery1(aBoolean);}, executorService);//创建线程,将任务放到线程池CompletableFuture<Void> query2 =CompletableFuture.runAsync(()-> {Boolean aBoolean = null;try {aBoolean = query2();} catch (InterruptedException e) {e.printStackTrace();}queryBO.setQuery2(aBoolean);},executorService);//创建线程,将任务放到线程池CompletableFuture<Void> query3=CompletableFuture.runAsync(()-> {Boolean aBoolean = null;try {aBoolean = query3();} catch (InterruptedException e) {e.printStackTrace();}queryBO.setQuery3(aBoolean);},executorService);//将这3个任务放到数组中CompletableFuture[] completableFutures=Stream.of(query1,query2,query3).collect(Collectors.toList()).toArray(new CompletableFuture[3]);//当数组里的任务都执行完,聚合一下,这行代码相当于将主线程阻塞住CompletableFuture.allOf(completableFutures).join();//打印时间戳long time = System.currentTimeMillis()-start;//allOf之后才会执行这句log,验证一下queryBOlog.info("查询结果{},时间差{}",queryBO,time);
}/*** 第一个查询,耗时3秒* @return* @throws InterruptedException*/
public Boolean query1()throws InterruptedException{log.info("进来了1");Thread.sleep(3000);return true;
}/*** 第二个查询,耗时1秒* @return* @throws InterruptedException*/
public Boolean query2()throws InterruptedException{log.info("进来了2");Thread.sleep(1000);return true;
}/*** 第三个查询,耗时5s* @return* @throws InterruptedException*/
public Boolean query3()throws InterruptedException{log.info("进来了3");Thread.sleep(5000);return false;
}
给一个QueryBO,Controller、Service代码都是不变的,上边有
@Data
public class QueryBO {private Boolean query1;private Boolean query2;private Boolean query3;
}
运行一下子,看看结果:
===> 不出意料的时间差是5秒左右,QueryBO中也有三个任务的返回值,说明三个查询是一起执行的。
===>重点代码是,以下两行,仔细研究
//将这3个任务放到数组中
CompletableFuture[] completableFutures=Stream.of(query1,query2,query3).collect(Collectors.toList()).toArray(new CompletableFuture[3]);//当数组里的任务都执行完,聚合一下,这行代码相当于将主线程阻塞住
CompletableFuture.allOf(completableFutures).join();
线程不安全问题
定义一个全局变量count=1;每个线程内都去操作这个count,让它+1操作,最后count的结果是什么?
多执行几次结果:
===>发现这个count并不是连续递增,说明多线程操作一个变量是不安全的,可能会被互相覆盖。
===>解决方法:加锁