寫在開頭
前段時間看了些java多線程的書和博文,但是在接下來倒沒有太多用到站绪,為了防止遺忘遭铺,準備總結一篇博文記錄一下。注:此文建議復習使用崇众。
多線程的寫法
多線程的實現方式大致有如下兩種:
- 繼承Thread類:extend Thread 實現 run 函數
- 實現Runnable接口:implements Runnable 實現run函數
線程安全
通過synchronized關鍵字實現線程間共享變量的數據安全掂僵。
Thread.currentThread()可以獲取當前線程實例。
線程調度
線程等待:使用sleep函數使線程等待相應時間
線程中止:使用推出標志顷歌、使用stop(已廢棄的方法不推薦锰蓬,可能產生無法預期的后果 如:會釋放鎖讓數據不一致)、使用interrupt(軟中斷眯漩,需要在run函數中判斷isInterrupted是否已中斷)
線程暫停:suspend暫停線程 resume恢復線程芹扭,但是這種使用方式有如下兩個缺點:1-暫停線程中的同步對象加鎖后suspend會導致鎖無法釋放舱卡,其他線程饑餓等待队萤。2-暫停線程中如果有傳入對象進行數據操作,可能會造成只操作一半舍杜,數據不同步。
另外既绩,yield方法也可以將線程暫停由其他的線程獲取CPU資源。
線程優(yōu)先級:setPriority可以設置線程優(yōu)先級私杜,線程優(yōu)先級具有繼承性救欧、規(guī)則性及隨機性。
多線程對象及并發(fā)訪問
實例變量非線程安全:多個線程共享一個實例變量時可能會出現非線程安全寄猩,可以給變量或變量方法加同步鎖(synchronized)
Synchronized不僅可以給方法加鎖也可以單獨給語句塊加鎖來提升線程運行效率骑疆。
synchronized(this){
//coding
}
synchronized同步代碼塊會將括號中的對象作為對象監(jiān)視器箍铭,在對象相同的代碼塊會呈現同步效果椎镣,也會與該對象的同步方法呈現同步效果诈火。
需要注意的是synchronized關鍵字加在static靜態(tài)方法時,持有的Class鎖冷守,會和持有該class實例化對象鎖的代碼呈現同步效果拍摇。
synchronized以string為鎖對象的時候需要注意string常量池的問題馆截,相同值的string持有一把鎖,所以一般不建議使用string作為鎖對象蜡娶。
volatile關鍵字
volatile是線程同步的輕量實現,性能上優(yōu)于synchronized幕随,且在多線程訪問時也不會造成阻塞赘淮,volatile可以保證多線程的數據可見性枢赔,但不能保證原子性踏拜,而synchronized可以同時保證原子性和可見性低剔,另外volatile只能修飾變量。
volatile的主要使用場合是在多線程可以感知實例變量被更改了姻锁,并且可以獲得最新的值使用猜欺。
原子類
使用原子類(如:AtomicInt)可以保證其方法的原子性,但是方法和方法之間的調用不是原子的涧黄。所以還是需要在代碼中使用synchronized赋荆。
線程間通信
線程(A)可以使用持有對象鎖的wait()方法,此時會釋放該對象的鎖春宣,然后等待其他線程(B)調用該對象的notify()方法喚起此線程(A)繼續(xù)執(zhí)行之后的代碼嫉你,另外notify會隨機喚醒一個此對象wait的線程,而notifyAll則喚醒全部嚷辅。
需要注意的是notify并不會釋放鎖潦蝇,當前notify線程會繼續(xù)運行直到釋放對象鎖后深寥,被喚起的線程才會開始執(zhí)行。
wait也可以使用long型參數作為超時自動喚醒的參數惋鹅,只要當時該對象鎖未被占用,則線程自動喚醒般卑。
使用線程(A)的join方法可以使當前線程等待該線程(A)完成run之后開始執(zhí)行爽雄,另外的join也可以傳入long參數作為超時自動喚醒的參數。join的內部實現是基于wait的叹谁,所以join也會釋放鎖乘盖。
前面提到了sleep也會阻塞線程使其等待,但是不同的是sleep不會釋放鎖析苫。
線程共享變量
類ThreadLocal可以在不同線程中實現各線程隔離的共享變量衩侥,不同線程的ThreadLocal的值將保持隔離、不會污染顿乒、父子線程也不會繼承泽谨。
而類InheritableThreadLocal可以在子線程取得父線程繼承下的值吧雹,但是在繼承之后子線程與父線程仍會保持隔離。
Lock的使用
除了使用synchronized關鍵字實現同步外雄卷,Java1.5新增了ReentrantLock也可以達成同步的效果蛤售。synchronized可以使用wait/notify進行線程間通信悴能。ReentranLock也提供了通信類Condition,該類可以使用await/signal/signalAll進行等待和喚醒漠酿,另外因為ReentranLock可以創(chuàng)建多個Condition,所以也可以實現點對點多路通知宇姚。同樣的,await需要先持有對象鎖進行l(wèi)ock浑劳。
以下簡單代碼示例:
public class Service
{
private Lock lock = new ReentrantLock();
public Condition conditionA = lock.newCondition();
public Condition conditionB = lock.newCondition();
public void waitA(){
lock.lock();
conditionA.await();
lock.unlock();
}
public void signalA(){
lock.lock();
conditionA.signal();
lock.unlock();
}
}
另外ReentrantLock有以下幾種方法:
- getHoldCount() 查詢當前線程鎖定的個數
- getQueueLength() 返回正在等待獲得該鎖定的線程數
- getWaitQueueLength(Condition c)返回等待該鎖定此條件(c)的線程數
- hasQueueThread(Thread t)查詢指定線程是否正在等待該鎖定
- hasWaiters(Condition c)查詢是否有線程等待該鎖定此條件(c)
剩余不再列舉
相對于完全互斥的ReentrantLock魔熏,ReentrantReadWriteLock在某些不需要操作實例變量的方法中,可以使用讀寫鎖(readlock/writelock)來加快運行效率兵罢。讀鎖共享滓窍、寫鎖與其他鎖互斥。
定時器
Timer使用schedule進行定時操作此蜈,第一個參數一般傳入具體的timertask裆赵。其余可以接受task運行的具體日期時間Datetime或者延遲執(zhí)行時間long跺嗽,也可以傳入間隔時間period(long型),以間隔時間循環(huán)運行task植兰。
另外timertask的cancel方法可以取消當前task的運行楣导,而timer的cancel將清除所有的任務隊列畜挨。
相對于schedule,還有scheduleAtFixedRate該方法基于上次任務結束后的時間開始進行循環(huán)毡咏。
寫在結尾
多線程是一門值得深入研究和探討的核心技術务冕,以上總結寫得比較簡單淺薄,如有遺漏請不吝提醒我落恼,基本參考自《Java多線程編程核心技術》离熏,非常感謝前輩們貢獻知識。