上一篇博文:Java多線程(一) —— 線程的狀態詳解中詳細介紹了線程的五種狀態及狀態間的轉換。本文着重介紹了線程安全的相關知識點,包括線程同步和鎖機制、線程間通訊以及相關面試題的總結html
多個線程在執行同一段代碼的時候,每次的執行結果和單線程執行的結果都是同樣的,不存在執行結果的二義性,就能夠稱做是線程安全的。面試
講到線程安全問題,實際上是指多線程環境下對共享資源的訪問可能會引發此共享資源的不一致性。所以,爲避免線程安全問題,應該避免多線程環境下對此共享資源的併發訪問。編程
線程安全問題可能是由全局變量和靜態變量引發的,當多個線程對共享數據只執行讀操做,不執行寫操做時,通常是線程安全的;當多個線程都執行寫操做時,須要考慮線程同步來解決線程安全問題。安全
線程同步:將操做共享數據的代碼行做爲一個總體,同一時間只容許一個線程執行,執行過程當中其餘線程不能參與執行。目的是爲了防止多個線程訪問一個數據對象時,對數據形成的破壞。多線程
(1)同步方法(synchronized)併發
對共享資源進行訪問的方法定義中加上synchronized關鍵字修飾,使得此方法稱爲同步方法。能夠簡單理解成對此方法進行了加鎖,其鎖對象爲當前方法所在的對象自身。多線程環境下,當執行此方法時,首先都要得到此同步鎖(且同時最多隻有一個線程可以得到),只有當線程執行完此同步方法後,纔會釋放鎖對象,其餘的線程纔有可能獲取此同步鎖,以此類推...格式以下:post
public synchronized void run() { // .... }
(2)同步代碼塊(synchronized)this
使用同步方法時,使得整個方法體都成爲了同步執行狀態,會使得可能出現同步範圍過大的狀況,因而,針對須要同步的代碼能夠直接另外一種同步方式——同步代碼塊來解決。格式以下:spa
synchronized (obj) { // .... }
其中,obj爲鎖對象,所以,選擇哪個對象做爲鎖是相當重要的。通常狀況下,都是選擇此共享資源對象做爲鎖對象。操作系統
(3)同步鎖(Lock)
使用Lock對象同步鎖能夠方便地解決選擇鎖對象的問題,惟一須要注意的一點是Lock對象須要與資源對象一樣具備一對一的關係。Lock對象同步鎖通常格式爲:
class X { // 顯示定義Lock同步鎖對象,此對象與共享資源具備一對一關係 private final Lock lock = new ReentrantLock(); public void m(){ // 加鎖 lock.lock(); //... 須要進行線程安全同步的代碼 // 釋放Lock鎖 lock.unlock(); } }
何時須要同步:
(1)可見性同步:在如下狀況中必須同步: 1)讀取上一次多是由另外一個線程寫入的變量 ;2)寫入下一次可能由另外一個線程讀取的變量
(2)一致性同步:當修改多個相關值時,您想要其它線程原子地看到這組更改—— 要麼看到所有更改,要麼什麼也看不到。
這適用於相關數據項(如粒子的位置和速率)和元數據項(如鏈表中包含的數據值和列表自身中的數據項的鏈)。
在某些狀況中,您沒必要用同步來將數據從一個線程傳遞到另外一個,由於 JVM 已經隱含地爲您執行同步。這些狀況包括:
鎖的原理:
wait():致使當前線程等待並使其進入到等待阻塞狀態。直到其餘線程調用該同步鎖對象的notify()或notifyAll()方法來喚醒此線程。
notify():喚醒在此同步鎖對象上等待的單個線程,若是有多個線程都在此同步鎖對象上等待,則會任意選擇其中某個線程進行喚醒操做,只有當前線程放棄對同步鎖對象的鎖定,纔可能執行被喚醒的線程。
notifyAll():喚醒在此同步鎖對象上等待的全部線程,只有當前線程放棄對同步鎖對象的鎖定,纔可能執行被喚醒的線程。
這三個方法主要都是用於多線程中,但實際上都是Object類中的本地方法。所以,理論上,任何Object對象均可以做爲這三個方法的主調,在實際的多線程編程中,只有同步鎖對象調這三個方法,才能完成對多線程間的線程通訊。
注意點:
1.wait()方法執行後,當前線程當即進入到等待阻塞狀態,其後面的代碼不會執行;
2.notify()/notifyAll()方法執行後,將喚醒此同步鎖對象上的(任意一個-notify()/全部-notifyAll())線程對象,可是,此時還並無釋放同步鎖對象,也就是說,若是notify()/notifyAll()後面還有代碼,還會繼續進行,知道當前線程執行完畢纔會釋放同步鎖對象;
3.notify()/notifyAll()執行後,若是右面有sleep()方法,則會使當前線程進入到阻塞狀態,可是同步對象鎖沒有釋放,依然本身保留,那麼必定時候後仍是會繼續執行此線程,接下來同2;
4.wait()/notify()/nitifyAll()完成線程間的通訊或協做都是基於不一樣對象鎖的,所以,若是是不一樣的同步對象鎖將失去意義,同時,同步對象鎖最好是與共享資源對象保持一一對應關係;
5.當wait線程喚醒後並執行時,是接着上次執行到的wait()方法代碼後面繼續往下執行的。
1. 線程和進程有什麼區別?
答:一個進程是一個獨立(self contained)的運行環境,它能夠被看做一個程序或者一個應用。而線程是在進程中執行的一個任務。線程是進程的子集,一個進程能夠有不少線程,每條線程並行執行不一樣的任務。不一樣的進程使用不一樣的內存空間,而全部的線程共享一片相同的內存空間。別把它和棧內存搞混,每一個線程都擁有單獨的棧內存用來存儲本地數據。
2. 如何在Java中實現線程?比較這種種方式
答:建立線程有兩種方式:
(1)繼承 Thread 類,擴展線程。
(2)實現 Runnable 接口。
繼承Thread類的方式有它固有的弊端,由於Java中繼承的單一性,繼承了Thread類就不能繼承其餘類了;同時也不符合繼承的語義,Dog跟Thread沒有直接的父子關係,繼承Thread只是爲了能擁有一些功能特性。
而實現Runnable接口,①避免了單一繼承的侷限性,②同時更符合面向對象的編程方式,即將線程對象進行單獨的封裝,③並且實現接口的方式下降了線程對象(Dog)和線程任務(run方法中的代碼)的耦合性,④如上面所述,可使用同一個Dog類的實例來建立並開啓多個線程,很是方便的實現資源的共享。實際上Thread類也是實現了Runnable接口。實際開發中可能是使用實現Runnable接口的方式。
3. 啓動一個線程是調用run()仍是start()方法?
答:啓動一個線程是調用start()方法,使線程所表明的虛擬處理機處於可運行狀態,這意味着它能夠由JVM 調度並執行,這並不意味着線程就會當即運行。run()方法是線程啓動後要進行回調(callback)的方法。
4. wait()和sleep()比較
共同點:
1). 他們都是在多線程的環境下,sleep()方法和對象的wait()方法均可以讓線程暫停執行,均可以在程序的調用處阻塞指定的毫秒數,並返回。
2). wait()和sleep()均可以經過interrupt()方法打斷線程的暫停狀態 ,從而使線程馬上拋出InterruptedException。
若是線程A但願當即結束線程B,則能夠對線程B對應的Thread實例調用interrupt方法。若是此刻線程B正在wait/sleep /join,則線程B會馬上拋出InterruptedException,在catch() {} 中直接return便可安全地結束線程。 須要注意的是,InterruptedException是線程本身從內部拋出的,並非interrupt()方法拋出的。對某一線程調用 interrupt()時,若是該線程正在執行普通的代碼,那麼該線程根本就不會拋出InterruptedException。可是,一旦該線程進入到 wait()/sleep()/join()後,就會馬上拋出InterruptedException 。
不一樣點:
1). Thread類的方法:sleep(),yield()等
Object類的方法:wait()和notify()等
2). 每一個對象都有一個鎖來控制同步訪問。Synchronized關鍵字能夠和對象的鎖交互,來實現線程的同步。
sleep()方法讓當前線程暫停執行指定的時間,將執行機會(CPU)讓給其餘線程,可是對象的鎖依然保持,休眠結束後線程會自動回到就緒狀態;
wait()方法致使當前線程放棄對象的鎖(線程暫停執行),進入對象的等待池(wait pool),只有調用對象的notify()方法(或notifyAll()方法)時才能喚醒等待池中的線程進入等鎖池(lock pool),若是線程從新得到對象的鎖就能夠進入就緒狀態。
3). wait,notify和notifyAll只能在同步控制方法或者同步控制塊裏面使用,而sleep能夠在任何地方使用
4). sleep必須捕獲異常,而wait,notify和notifyAll不須要捕獲異常
因此sleep()和wait()方法的最大區別是:
sleep()睡眠時,保持對象鎖,仍然佔有該鎖;
而wait()睡眠時,釋放對象鎖。
可是wait()和sleep()均可以經過interrupt()方法打斷線程的暫停狀態,從而使線程馬上拋出InterruptedException(但不建議使用該方法)。
5. sleep()方法和yield()方法有什麼區別?
① sleep()方法給其餘線程運行機會時不考慮線程的優先級,所以會給低優先級的線程以運行的機會;yield()方法只會給相同優先級或更高優先級的線程以運行的機會;
② 線程執行sleep()方法後轉入阻塞(blocked)狀態,而執行yield()方法後轉入就緒(ready)狀態;
③ sleep()方法須要聲明拋出InterruptedException,而yield()方法沒有聲明任何異常;
④ sleep()方法比yield()方法(跟操做系統CPU調度相關)具備更好的可移植性。
6. 線程類的一些經常使用方法:
7. 同步代碼塊和同步方法的區別
二者的區別主要體如今同步鎖上面。對於實例的同步方法,由於只能使用this來做爲同步鎖,若是一個類中須要使用到多個鎖,爲了不鎖的衝突,必然須要使用不一樣的對象,這時候同步方法不能知足需求,只能使用同步代碼塊(同步代碼塊能夠傳入任意對象);或者多個類中須要使用到同一個鎖,這時候多個類的實例this顯然是不一樣的,也只能使用同步代碼塊,傳入同一個對象。
8. 對比synchronized和Lock
1)、synchronized是關鍵字,就和if...else...同樣,是語法層面的實現,所以synchronized獲取鎖以及釋放鎖都是Java虛擬機幫助用戶完成的;ReentrantLock是類層面的實現,所以鎖的獲取以及鎖的釋放都須要用戶本身去操做。特別再次提醒,ReentrantLock在lock()完了,必定要手動unlock(),通常放在finally語句塊中。
2)、synchronized簡單,簡單意味着不靈活,而ReentrantLock的鎖機制給用戶的使用提供了極大的靈活性。這點在Hashtable和ConcurrentHashMap中體現得淋漓盡致。synchronized一鎖就鎖整個Hash表,而ConcurrentHashMap則利用ReentrantLock實現了鎖分離,鎖的只是segment而不是整個Hash表
3)、synchronized是不公平鎖,而ReentrantLock能夠指定鎖是公平的仍是非公平的
4)、synchronized實現等待/通知機制通知的線程是隨機的,ReentrantLock實現等待/通知機制能夠有選擇性地通知
5)、和synchronized相比,ReentrantLock提供給用戶多種方法用於鎖信息的獲取,好比能夠知道lock是否被當前線程獲取、lock被同一個線程調用了幾回、lock是否被任意線程獲取等等
總結起來,我認爲若是隻須要鎖定簡單的方法、簡單的代碼塊,那麼考慮使用synchronized,複雜的多線程處理場景下能夠考慮使用ReentrantLock。
Voiatile關鍵字:
volatile關鍵字是Java併發的最輕量級實現,本質上有兩個功能,在生成的彙編語句中加入LOCK關鍵字和內存屏障
做用就是保證每一次線程load和write兩個操做,都會直接從主內存中進行讀取和覆蓋,而非普通變量從線程內的工做空間(默認各位已經熟悉Java多線程內存模型)
但它有一個很致命的缺點,致使它的使用範圍很少,就是他只保證在讀取和寫入這兩個過程是線程安全的。若是咱們對一個volatile修飾的變量進行多線程 下的自增操做,仍是會出現線程安全問題。根本緣由在於volatile關鍵字沒法對自增進行安全性修飾,由於自增分爲三步,讀取-》+1-》寫入。中間多 個線程同時執行+1操做,仍是會出現線程安全性問題。
參考連接:
http://www.importnew.com/21136.html
http://lavasoft.blog.51cto.com/62575/99155/
http://www.cnblogs.com/lwbqqyumidi/p/3821389.html