前言
最近一段時間在整公司項目里一個功能的優化,用到了多線程處理。期間也是踩了不少的坑,在這里想說下我遇到的問題和注意事項。以及怎樣知道啟動的那些多線程都處理完畢這些問題。
實現Runnable接口類需要注意事項
我這里用的多線程,是用了實現Runnable接口,這樣的話,要比繼承Thread類更加的靈活。畢竟類只能單繼承,但可以多實現。
1.事務失效
我實現Runnable接口的類,是處理業務的handler類,在spring配置里面是默認給這些類添加事務的。所以我當時直接在這個類里面寫了業務代碼。到測試的時候發現,如果業務方法里報Runtime異常,這個類里面的一些更新方法居然不回滾,直接提交了。
當時也是試了很多方法,手動給這個類加事務的注解、開辟新事物,都不行。后來查閱資料,發現在Runnable實現類里,是不支持事務的。那我就新寫了一個類,把主要的業務方法全放到那個類里,再測試,發現事務可以正常回滾了。
2.注解無效
在實現Runnable接口的類里,本來想用spring提供的@Autowired注解來自動注入類呢,發現在run方法里,調用注入的類,報空指針。后來明白,實現Runnable接口的類不受spring監管,所以spring的一些注解就不能使用了。
解決辦法:
1.可以參考下面這樣寫,可以獲得你想要用的類。
ApplicationContext ctx = new ApplicationContext(); ctx.getBean(你想要獲取的類名.class);
2.那就是在調用多線程實現類之前,在其他類里用spring的一些注解,獲得你想要的類,然后通過參數方式,傳到多線程實現類里面。(我是采用的這個方式。)
如何知道多個線程都執行完畢了呢?
使用多線程,主要就是為了提高程序的運行效率。一般情況下,分配完線程,讓那些線程去執行就行了,也不需要關心他們都什么時候執行完畢了。但是有些情況下,知曉那些線程都什么時候執行完畢,確實很有用。
我實現的那個功能就是放在定時器里面的,知曉定時器什么時候開始,什么時候執行完畢,在完畢的時候執行一些發郵件的一些功能,是很有用的。如果是單線程,那就直接把那些方法日志啥的放到最后執行就可以了。
但是開辟了多個線程,往往是多線程還在執行,主線程不等那些子線程,就先自己執行完了,這時候,放到最后執行的那些功能就不行了,因為子線程還沒執行完,主線程就把最后的那些“收尾”功能給執行了,肯定不合適。
thread.Join方法,可以讓交替執行的線程變成順序的執行,但這樣跟單線程就沒啥區別了。
后來,我想了一個辦法。代碼如下:
//線程池 private ExecutorService threadPool; //分配線程任務 for (int i=0; i<5; i++) { threadPool.execute(new RunHandler()); } //關閉線程池,此時執行的線程不會立刻關閉,而是線程池不再接受新的線程請求了,線程執行完會被回收掉。 threadPool.shutdown(); while(true){ if(threadPool.isTerminated()) {//判斷線程是否執行完畢,不是就休眠主線程。 //如果子線程們都執行完畢,就會進這個判斷,然后會跳出這個循環。這樣就達到了主線程等待子線程們 //都執行完了,才去執行其他的代碼。 break; } Thread.sleep(1000);//主線程睡眠1秒 } //這里寫執行完畢的日志或者最后的“收尾”功能。
這個就是我目前使用的方法。當然,能實現這個功能的方法還有很多,我選的也是比較好實現容易理解,效率算是比較高的一種吧。
最后
以上就是我發現和解決的一些常見的問題。由於能力有限,如有錯誤,敬請諒解。
寫好一個多線程的功能,以上那些注意事項往往根本不夠。最主要的是解決多線程之間的沖突,如何避免多線程操作導致變量數據的錯亂和引發的數據庫保存數據的異常等問題。這些是值得推敲和反復琢磨的,加鎖一般能解決這些問題,但是不合理的加鎖和使用的加鎖方式的不同,可能會導致多線程執行起來的效率不盡人意。
有時間,我會單獨寫一篇多線程避免沖突錯誤的文章,來供大家參考下。