多线程实现
1、要写一个配置类开启多线程
配置类实现AsyncConfigurer接口,并重写getAsyncExecutor()方法返回一个Executor,并用@EnableAsync
注解标注。
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
// 核心线程大小
threadPoolTaskExecutor.setCorePoolSize(8);
// 最大线程大小
threadPoolTaskExecutor.setMaxPoolSize(16);
// 队列大小
threadPoolTaskExecutor.setQueueCapacity(50);
// 初始化
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
}
2、在需要多线程执行的方法上标注@Async
注解
如果@Async
标注在类上,则该类的所有方法都是异步方法。
@Service
@Async
public class AsyncService {
public void fun1(int i) {
System.out.println("fun1----" + i);
}
public void fun2(int i) {
System.out.println("fun2====" + i);
}
}
3、测试效果
@Controller
@RequestMapping("/hello")
public class HelloController {
@Autowired
private AsyncService asyncService;
@GetMapping("/testAsync")
public String testAsync() {
for (int i = 0; i < 100; i++) {
asyncService.fun1(i);
asyncService.fun2(i);
}
return "success";
}
}
访问该接口,输出结果就会看到多线程效果
// 部分输出结果
fun1----0
fun1----4
fun2====4
fun1----5
fun2====5
fun1----6
fun2====6
fun1----7
fun2====7
fun1----8
fun2====8
fun1----9
fun2====9
fun1----10
实际输出中的报错
抛出RejectedExecutionException拒绝执行异常。
java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@24a8a5ec rejected from java.util.concurrent.ThreadPoolExecutor@6719f3a5[Running, pool size = 16, active threads = 16, queued tasks = 50, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063) ~[na:1.8.0_161]
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830) [na:1.8.0_161]
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379) [na:1.8.0_161]
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134) ~[na:1.8.0_161]
要分析报错的原因首先要指导Executor的执行原理是什么。
ThreadPoolTaskExecutor的一些参数
在配置类中配置了一些ThreadPoolTaskExecutor的corePoolSize核心线程池、maxPoolSize最大线程池、queneCapacity队列大小。ThreadPoolTaskExecutor会根据corePoolSize和maxPoolSize来调整线程池大小。
- 当新任务执行时,如果运行的线程小于corePoolSize就会创建新的线程,即使其他线程是空闲状态。
- 如果创建的线程数量达到了corePoolSize,就会把新任务放到队列中等待执行。
- 如果队列满了,并且运行的线程小于maxPoolSize,就会创建新的线程。
- 如果线程达到了maxPoolSize,且队列也满了,再开启新的任务就会报错RejectedExecutionException
- 如果corePoolSize等于maxPoolSize,表示创建了固定大小的线程池。
- 如果设置maxPoolSize的值为Integer.MAX_VALUE表示创建了无界的线程池
队列的三种策略
直接提交
工作队列的默认选项是synchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界maximumPoolSizes以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增加的可能性。
无界队列使用无界队列(例如,不具有预定义容量的LinkedBlockingQueue)将导致在所有corePoolSize线程都忙时新任务在队列中等待。这样,创建的线程就不会超过corePoolSize(因此,maximumPoolSize的值也就无效了)。
有界队列当使用有限的maximumPoolSizes时,有界队列(如ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度的降低CPU使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞,则系统可能为超过您许可的更多线程安排时间,使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样可会降低吞吐量。
此外,引发RejectedExecutionException的另一种原因是,显式的调用了线程池的shutdown()方法。
参考: