SpringBoot使用多线程简单方法:地址
线程安全查阅资料参考:地址
背景: 经过上述资料查看,我想写个方法(依靠notify()唤醒,依靠wait()等待)实现两个线程轮流打印。
配置多线程方法1
实现:
1.线程池配置(配置完线程池后直接 @Async就可以异步调用注释的方法了)
@Configuration
@EnableAsync
public class AsyncConfiguration {
@Bean("ceshiAsync")
public Executor doSomethingExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数:线程池创建时候初始化的线程数
executor.setCorePoolSize(3);
// 最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setMaxPoolSize(4);
// 缓冲队列:用来缓冲执行任务的队列
executor.setQueueCapacity(10);
// 允许线程的空闲时间60秒:当超过了核心线程之外的线程在空闲时间到达之后会被销毁
executor.setKeepAliveSeconds(6);
// 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
executor.setThreadNamePrefix("测试线程-");
// 缓冲队列满了之后的拒绝策略:由调用线程处理(一般是主线程)
// executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
executor.initialize();
return executor;
}
}
2.轮流打印的方法(此次是测试锁与多线程使用)( 两个方法完全一样,主要依靠list.notify(); list.wait();)
@Async("ceshiAsync")
public void aaa(List<Integer> list) {
System.out.println("线程A等待获取i锁");
synchronized (list) {
try {
System.out.println("线程A获取了i锁");
while (list.get(0)>-1){
log.info("线程A:"+list.get(0));
list.set(0,list.get(0)-1);
list.notify();
list.wait();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Async("ceshiAsync")
public void bbb(List<Integer> list) {
System.out.println("线程B等待获取i锁");
synchronized (list) {
try {
System.out.println("线程B获取了i锁");
while (list.get(0)>-1){
log.info("线程B:"+list.get(0));
list.set(0,list.get(0)-1);
list.notify();
list.wait();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.调用方法
List<Integer> list=new ArrayList<>();
@Test
@GetMapping(value = "ccc")
public void ccc(){
list.add(100);
ceshiService.aaa(list);
System.out.println("-----------");
ceshiService.bbb(list);
}
实现效果
踩坑
1.@Async注解未生效
原因:测试图方便把注解的方法直接写在方法,调用时直接用方法名调用,固注解未生效,这种写法会导致注解失效,例如事务的注解,但是与事务不同的时注入自身时记得添加 @Lazy注解,避免循环依赖。
2.current thread is not owner(当前线程不是所有者)错误
不要直接用Integer对象当锁,查询资料Integer内部的int值是不可改变的,估计是值改变时地址变了导致报这个错。
3.执行到一半报错
效果:
原因:有些测试不适合用@Test注解,例如这里线程池出问题,事后想想这报错完全是理所应当的事情,亏我还在怀疑是notify方法提前把锁释放了导致wait执行时没锁了。罪过罪过
配置多线程方法2
实现:
1.线程池配置(使用时ScheduledAsync.getThreadPool().submit(() -> 要执行的方法;),对比第一种优点是对于已经写好的业务不用把需要异步执行的部分单独拿出来加注解,Runtime.getRuntime().availableProcessors();是获取可用的计算资源。
@Configuration
@EnableAsync
public class ScheduledAsync{
public static final int CPU_NUM = Runtime.getRuntime().availableProcessors();
private static final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CPU_NUM, CPU_NUM,
1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(CPU_NUM + 10));
static {
// 设置拒绝策略
threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 空闲队列存活时间
threadPool.setKeepAliveTime(20, TimeUnit.SECONDS);
}
public static ThreadPoolExecutor getThreadPool(){
return threadPool;
}
}
阻塞的使用
若希望前面一段代码都执行完再统一执行下边可以使用CountDownLatch进行阻塞
例如代码
List<String> aaas= Arrays.asList("1111","2222","3333","4444");
aaas.forEach(aaa-> {
ScheduledAsync.getThreadPool().submit(() -> {
System.out.println("------------执行"+aaa);
try {
Thread.sleep(2000);
System.out.println("------------休眠后执行"+aaa);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
});
System.out.println("循环外");
添加阻塞后:
List<String> aaas= Arrays.asList("1111","2222","3333","4444");
CountDownLatch countDownLatch = new CountDownLatch(aaas.size());//设定一个数目
aaas.forEach(aaa-> {
ScheduledAsync.getThreadPool().submit(() -> {
System.out.println("------------执行"+aaa);
try {
Thread.sleep(2000);
System.out.println("------------休眠后执行"+aaa);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
countDownLatch.countDown();//可理解为计数器+1
}
});
});
countDownLatch.await();//达到设定数目后执行
System.out.println("循环外");
效果