多線程實現
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()方法。
參考: