上一篇博文:Java多線程(一) —— 線程的狀態(tài)詳解中詳細(xì)介紹了線程的五種狀態(tài)及狀態(tài)間的轉(zhuǎn)換。本文著重介紹了線程安全的相關(guān)知識(shí)點(diǎn),包括線程同步和鎖機(jī)制、線程間通信以及相關(guān)面試題的總結(jié)

一、線程安全

多個(gè)線程在執(zhí)行同一段代碼的時(shí)候,每次的執(zhí)行結(jié)果和單線程執(zhí)行的結(jié)果都是一樣的,不存在執(zhí)行結(jié)果的二義性,就可以稱作是線程安全的。

講到線程安全問題,其實(shí)是指多線程環(huán)境下對(duì)共享資源的訪問可能會(huì)引起此共享資源的不一致性。因此,為避免線程安全問題,應(yīng)該避免多線程環(huán)境下對(duì)此共享資源的并發(fā)訪問。

線程安全問題多是由全局變量和靜態(tài)變量引起的,當(dāng)多個(gè)線程對(duì)共享數(shù)據(jù)只執(zhí)行讀操作,不執(zhí)行寫操作時(shí),一般是線程安全的;當(dāng)多個(gè)線程都執(zhí)行寫操作時(shí),需要考慮線程同步來解決線程安全問題。

 

二、線程同步(synchronized/Lock)

線程同步:將操作共享數(shù)據(jù)的代碼行作為一個(gè)整體,同一時(shí)間只允許一個(gè)線程執(zhí)行,執(zhí)行過程中其他線程不能參與執(zhí)行。目的是為了防止多個(gè)線程訪問一個(gè)數(shù)據(jù)對(duì)象時(shí),對(duì)數(shù)據(jù)造成的破壞。

(1)同步方法(synchronized)

對(duì)共享資源進(jìn)行訪問的方法定義中加上synchronized關(guān)鍵字修飾,使得此方法稱為同步方法。可以簡(jiǎn)單理解成對(duì)此方法進(jìn)行了加鎖,其鎖對(duì)象為當(dāng)前方法所在的對(duì)象自身。多線程環(huán)境下,當(dāng)執(zhí)行此方法時(shí),首先都要獲得此同步鎖(且同時(shí)最多只有一個(gè)線程能夠獲得),只有當(dāng)線程執(zhí)行完此同步方法后,才會(huì)釋放鎖對(duì)象,其他的線程才有可能獲取此同步鎖,以此類推...格式如下:

public synchronized void run() {        
     // ....}

 

(2)同步代碼塊(synchronized)

使用同步方法時(shí),使得整個(gè)方法體都成為了同步執(zhí)行狀態(tài),會(huì)使得可能出現(xiàn)同步范圍過大的情況,于是,針對(duì)需要同步的代碼可以直接另一種同步方式——同步代碼塊來解決。格式如下:

synchronized (obj) {        
     // ....}

其中,obj為鎖對(duì)象,因此,選擇哪一個(gè)對(duì)象作為鎖是至關(guān)重要的。一般情況下,都是選擇此共享資源對(duì)象作為鎖對(duì)象。

 

(3)同步鎖(Lock)

 使用Lock對(duì)象同步鎖可以方便地解決選擇鎖對(duì)象的問題,唯一需要注意的一點(diǎn)是Lock對(duì)象需要與資源對(duì)象同樣具有一對(duì)一的關(guān)系。Lock對(duì)象同步鎖一般格式為:

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

class X {    // 顯示定義Lock同步鎖對(duì)象,此對(duì)象與共享資源具有一對(duì)一關(guān)系
    private final Lock lock = new ReentrantLock();    
    public void m(){        // 加鎖        lock.lock();        
        //...  需要進(jìn)行線程安全同步的代碼        
        // 釋放Lock鎖        lock.unlock();
    }
}

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

 

什么時(shí)候需要同步:

(1)可見性同步:在以下情況中必須同步: 1)讀取上一次可能是由另一個(gè)線程寫入的變量 ;2)寫入下一次可能由另一個(gè)線程讀取的變量

(2)一致性同步:當(dāng)修改多個(gè)相關(guān)值時(shí),您想要其它線程原子地看到這組更改—— 要么看到全部更改,要么什么也看不到。

這適用于相關(guān)數(shù)據(jù)項(xiàng)(如粒子的位置和速率)和元數(shù)據(jù)項(xiàng)(如鏈表中包含的數(shù)據(jù)值和列表自身中的數(shù)據(jù)項(xiàng)的鏈)。

在某些情況中,您不必用同步來將數(shù)據(jù)從一個(gè)線程傳遞到另一個(gè),因?yàn)?JVM 已經(jīng)隱含地為您執(zhí)行同步。這些情況包括:

  1. 由靜態(tài)初始化器(在靜態(tài)字段上或 static{} 塊中的初始化器)

  2. 初始化數(shù)據(jù)時(shí) 

  3. 訪問 final 字段時(shí)

  4. 在創(chuàng)建線程之前創(chuàng)建對(duì)象時(shí) 

  5. 線程可以看見它將要處理的對(duì)象時(shí)

 

鎖的原理:

  • Java中每個(gè)對(duì)象都有一個(gè)內(nèi)置鎖

  • 當(dāng)程序運(yùn)行到非靜態(tài)的synchronized同步方法上時(shí),自動(dòng)獲得與正在執(zhí)行代碼類的當(dāng)前實(shí)例(this實(shí)例)有關(guān)的鎖。獲得一個(gè)對(duì)象的鎖也稱為獲取鎖、鎖定對(duì)象、在對(duì)象上鎖定或在對(duì)象上同步。

  • 當(dāng)程序運(yùn)行到synchronized同步方法或代碼塊時(shí)才該對(duì)象鎖才起作用。

  • 一個(gè)對(duì)象只有一個(gè)鎖。所以,如果一個(gè)線程獲得該鎖,就沒有其他線程可以獲得鎖,直到第一個(gè)線程釋放(或返回)鎖。這也意味著任何其他線程都不能進(jìn)入該對(duì)象上的synchronized方法或代碼塊,直到該鎖被釋放。

  • 釋放鎖是指持鎖線程退出了synchronized同步方法或代碼塊。

鎖與同步要點(diǎn):

1)、只能同步方法,而不能同步變量和類;

2)、每個(gè)對(duì)象只有一個(gè)鎖;當(dāng)提到同步時(shí),應(yīng)該清楚在什么上同步?也就是說,在哪個(gè)對(duì)象上同步?

3)、不必同步類中所有的方法,類可以同時(shí)擁有同步和非同步方法。

4)、如果兩個(gè)線程要執(zhí)行一個(gè)類中的synchronized方法,并且兩個(gè)線程使用相同的實(shí)例來調(diào)用方法,那么一次只能有一個(gè)線程能夠執(zhí)行方法,另一個(gè)需要等待,直到鎖被釋放。也就是說:如果一個(gè)線程在對(duì)象上獲得一個(gè)鎖,就沒有任何其他線程可以進(jìn)入(該對(duì)象的)類中的任何一個(gè)同步方法。

5)、如果線程擁有同步和非同步方法,則非同步方法可以被多個(gè)線程自由訪問而不受鎖的限制。

6)、線程睡眠時(shí),它所持的任何鎖都不會(huì)釋放。

7)、線程可以獲得多個(gè)鎖。比如,在一個(gè)對(duì)象的同步方法里面調(diào)用另外一個(gè)對(duì)象的同步方法,則獲取了兩個(gè)對(duì)象的同步鎖。

8)、同步損害并發(fā)性,應(yīng)該盡可能縮小同步范圍。同步不但可以同步整個(gè)方法,還可以同步方法中一部分代碼塊。

9)、在使用同步代碼塊時(shí)候,應(yīng)該指定在哪個(gè)對(duì)象上同步,也就是說要獲取哪個(gè)對(duì)象的鎖。

10)、同步靜態(tài)方法,需要一個(gè)用于整個(gè)類對(duì)象的鎖,這個(gè)對(duì)象是就是這個(gè)類(XXX.class)。

 

線程不能獲得鎖會(huì)怎么樣:如果線程試圖進(jìn)入同步方法,而其鎖已經(jīng)被占用,則線程在該對(duì)象上被阻塞。實(shí)質(zhì)上,線程進(jìn)入該對(duì)象的的一種池中,必須在哪里等待,直到其鎖被釋放,該線程再次變?yōu)榭蛇\(yùn)行或運(yùn)行為止。

 

線程死鎖:當(dāng)兩個(gè)線程被阻塞,每個(gè)線程都在等待另一個(gè)線程時(shí)就發(fā)生死鎖。有一些設(shè)計(jì)方法能幫助避免死鎖,如始終按照預(yù)定義的順序獲取鎖這一策略。

 

線程同步小結(jié)

 

1、線程同步的目的是為了保護(hù)多個(gè)線程反問一個(gè)資源時(shí)對(duì)資源的破壞。

2、線程同步方法是通過來實(shí)現(xiàn),每個(gè)對(duì)象都有且僅有一個(gè)鎖,這個(gè)鎖與一個(gè)特定的對(duì)象關(guān)聯(lián),線程一旦獲取了對(duì)象鎖,其他訪問該對(duì)象的線程就無法再訪問該對(duì)象的其他同步方法。

3、對(duì)于靜態(tài)同步方法,鎖是針對(duì)這個(gè)類的,鎖對(duì)象是該類的Class對(duì)象。靜態(tài)和非靜態(tài)方法的鎖互不干預(yù)。一個(gè)線程獲得鎖,當(dāng)在一個(gè)同步方法中訪問另外對(duì)象上的同步方法時(shí),會(huì)獲取這兩個(gè)對(duì)象鎖。

4、對(duì)于同步,要時(shí)刻清醒在哪個(gè)對(duì)象上同步,這是關(guān)鍵。

5、編寫線程安全的類,需要時(shí)刻注意對(duì)多個(gè)線程競(jìng)爭(zhēng)訪問資源的邏輯和安全做出正確的判斷,對(duì)“原子”操作做出分析,并保證原子操作期間別的線程無法訪問競(jìng)爭(zhēng)資源。

6、當(dāng)多個(gè)線程等待一個(gè)對(duì)象鎖時(shí),沒有獲取到鎖的線程將發(fā)生阻塞。

7、死鎖是線程間相互等待鎖鎖造成的,在實(shí)際中發(fā)生的概率非常的小。真讓你寫個(gè)死鎖程序,不一定好使,呵呵。但是,一旦程序發(fā)生死鎖,程序?qū)⑺赖簟?/p>

 

三、線程通信:wait()/notify()/notifyAll()

wait():導(dǎo)致當(dāng)前線程等待并使其進(jìn)入到等待阻塞狀態(tài)。直到其他線程調(diào)用該同步鎖對(duì)象的notify()或notifyAll()方法來喚醒此線程。

  • void wait(long timeout) -- 導(dǎo)致當(dāng)前線程等待,直到其他線程調(diào)用此對(duì)象的 notify() 方法或 notifyAll() 方法,或者超過指定的時(shí)間量。 

  • void wait(long timeout, int nanos) -- 導(dǎo)致當(dāng)前線程等待,直到其他線程調(diào)用此對(duì)象的 notify() 方法或 notifyAll() 方法,或者其他某個(gè)線程中斷當(dāng)前線程,或者已超過某個(gè)實(shí)際時(shí)間量。

notify():喚醒在此同步鎖對(duì)象上等待的單個(gè)線程,如果有多個(gè)線程都在此同步鎖對(duì)象上等待,則會(huì)任意選擇其中某個(gè)線程進(jìn)行喚醒操作,只有當(dāng)前線程放棄對(duì)同步鎖對(duì)象的鎖定,才可能執(zhí)行被喚醒的線程。

notifyAll():?jiǎn)拘言诖送芥i對(duì)象上等待的所有線程,只有當(dāng)前線程放棄對(duì)同步鎖對(duì)象的鎖定,才可能執(zhí)行被喚醒的線程。

  這三個(gè)方法主要都是用于多線程中,但實(shí)際上都是Object類中的本地方法。因此,理論上,任何Object對(duì)象都可以作為這三個(gè)方法的主調(diào),在實(shí)際的多線程編程中,只有同步鎖對(duì)象調(diào)這三個(gè)方法,才能完成對(duì)多線程間的線程通信。

 

注意點(diǎn):

1.wait()方法執(zhí)行后,當(dāng)前線程立即進(jìn)入到等待阻塞狀態(tài),其后面的代碼不會(huì)執(zhí)行;

2.notify()/notifyAll()方法執(zhí)行后,將喚醒此同步鎖對(duì)象上的(任意一個(gè)-notify()/所有-notifyAll())線程對(duì)象,但是,此時(shí)還并沒有釋放同步鎖對(duì)象,也就是說,如果notify()/notifyAll()后面還有代碼,還會(huì)繼續(xù)進(jìn)行,知道當(dāng)前線程執(zhí)行完畢才會(huì)釋放同步鎖對(duì)象;

3.notify()/notifyAll()執(zhí)行后,如果右面有sleep()方法,則會(huì)使當(dāng)前線程進(jìn)入到阻塞狀態(tài),但是同步對(duì)象鎖沒有釋放,依然自己保留,那么一定時(shí)候后還是會(huì)繼續(xù)執(zhí)行此線程,接下來同2;

4.wait()/notify()/nitifyAll()完成線程間的通信或協(xié)作都是基于不同對(duì)象鎖的,因此,如果是不同的同步對(duì)象鎖將失去意義,同時(shí),同步對(duì)象鎖最好是與共享資源對(duì)象保持一一對(duì)應(yīng)關(guān)系;

5.當(dāng)wait線程喚醒后并執(zhí)行時(shí),是接著上次執(zhí)行到的wait()方法代碼后面繼續(xù)往下執(zhí)行的。

 

四、相關(guān)面試題

1. 線程和進(jìn)程有什么區(qū)別?
答:一個(gè)進(jìn)程是一個(gè)獨(dú)立(self contained)的運(yùn)行環(huán)境,它可以被看作一個(gè)程序或者一個(gè)應(yīng)用。而線程是在進(jìn)程中執(zhí)行的一個(gè)任務(wù)。線程是進(jìn)程的子集,一個(gè)進(jìn)程可以有很多線程,每條線程并行執(zhí)行不同的任務(wù)。不同的進(jìn)程使用不同的內(nèi)存空間,而所有的線程共享一片相同的內(nèi)存空間。別把它和棧內(nèi)存搞混,每個(gè)線程都擁有單獨(dú)的棧內(nèi)存用來存儲(chǔ)本地?cái)?shù)據(jù)。

2. 如何在Java中實(shí)現(xiàn)線程?比較這種種方式
答:創(chuàng)建線程有兩種方式:
(1)繼承 Thread 類,擴(kuò)展線程。
(2)實(shí)現(xiàn) Runnable 接口。

繼承Thread類的方式有它固有的弊端,因?yàn)镴ava中繼承的單一性,繼承了Thread類就不能繼承其他類了;同時(shí)也不符合繼承的語義,Dog跟Thread沒有直接的父子關(guān)系,繼承Thread只是為了能擁有一些功能特性。

而實(shí)現(xiàn)Runnable接口,①避免了單一繼承的局限性,②同時(shí)更符合面向?qū)ο蟮木幊谭绞?,即將線程對(duì)象進(jìn)行單獨(dú)的封裝,③而且實(shí)現(xiàn)接口的方式降低了線程對(duì)象(Dog)和線程任務(wù)(run方法中的代碼)的耦合性,④如上面所述,可以使用同一個(gè)Dog類的實(shí)例來創(chuàng)建并開啟多個(gè)線程,非常方便的實(shí)現(xiàn)資源的共享。實(shí)際上Thread類也是實(shí)現(xiàn)了Runnable接口。實(shí)際開發(fā)中多是使用實(shí)現(xiàn)Runnable接口的方式。

3. 啟動(dòng)一個(gè)線程是調(diào)用run()還是start()方法?
答:?jiǎn)?dòng)一個(gè)線程是調(diào)用start()方法,使線程所代表的虛擬處理機(jī)處于可運(yùn)行狀態(tài),這意味著它可以由JVM 調(diào)度并執(zhí)行,這并不意味著線程就會(huì)立即運(yùn)行。run()方法是線程啟動(dòng)后要進(jìn)行回調(diào)(callback)的方法。

4. wait()和sleep()比較

共同點(diǎn): 
1). 他們都是在多線程的環(huán)境下,sleep()方法和對(duì)象的wait()方法都可以讓線程暫停執(zhí)行,都可以在程序的調(diào)用處阻塞指定的毫秒數(shù),并返回。 
2). wait()和sleep()都可以通過interrupt()方法打斷線程的暫停狀態(tài) ,從而使線程立刻拋出InterruptedException。 
如果線程A希望立即結(jié)束線程B,則可以對(duì)線程B對(duì)應(yīng)的Thread實(shí)例調(diào)用interrupt方法。如果此刻線程B正在wait/sleep /join,則線程B會(huì)立刻拋出InterruptedException,在catch() {} 中直接return即可安全地結(jié)束線程。 需要注意的是,InterruptedException是線程自己從內(nèi)部拋出的,并不是interrupt()方法拋出的。對(duì)某一線程調(diào)用 interrupt()時(shí),如果該線程正在執(zhí)行普通的代碼,那么該線程根本就不會(huì)拋出InterruptedException。但是,一旦該線程進(jìn)入到 wait()/sleep()/join()后,就會(huì)立刻拋出InterruptedException 。 

不同點(diǎn): 
1). Thread類的方法:sleep(),yield()等 
     Object類的方法:wait()和notify()等 
2). 每個(gè)對(duì)象都有一個(gè)鎖來控制同步訪問。Synchronized關(guān)鍵字可以和對(duì)象的鎖交互,來實(shí)現(xiàn)線程的同步。 
     sleep()方法讓當(dāng)前線程暫停執(zhí)行指定的時(shí)間,將執(zhí)行機(jī)會(huì)(CPU)讓給其他線程,但是對(duì)象的鎖依然保持,休眠結(jié)束后線程會(huì)自動(dòng)回到就緒狀態(tài);

     wait()方法導(dǎo)致當(dāng)前線程放棄對(duì)象的鎖(線程暫停執(zhí)行),進(jìn)入對(duì)象的等待池(wait pool),只有調(diào)用對(duì)象的notify()方法(或notifyAll()方法)時(shí)才能喚醒等待池中的線程進(jìn)入等鎖池(lock pool),如果線程重新獲得對(duì)象的鎖就可以進(jìn)入就緒狀態(tài)。
3). wait,notify和notifyAll只能在同步控制方法或者同步控制塊里面使用,而sleep可以在任何地方使用 
4). sleep必須捕獲異常,而wait,notify和notifyAll不需要捕獲異常
所以sleep()和wait()方法的最大區(qū)別是:
  sleep()睡眠時(shí),保持對(duì)象鎖,仍然占有該鎖;
  而wait()睡眠時(shí),釋放對(duì)象鎖。
但是wait()和sleep()都可以通過interrupt()方法打斷線程的暫停狀態(tài),從而使線程立刻拋出InterruptedException(但不建議使用該方法)。

5. sleep()方法和yield()方法有什么區(qū)別?
① sleep()方法給其他線程運(yùn)行機(jī)會(huì)時(shí)不考慮線程的優(yōu)先級(jí),因此會(huì)給低優(yōu)先級(jí)的線程以運(yùn)行的機(jī)會(huì);yield()方法只會(huì)給相同優(yōu)先級(jí)或更高優(yōu)先級(jí)的線程以運(yùn)行的機(jī)會(huì);
② 線程執(zhí)行sleep()方法后轉(zhuǎn)入阻塞(blocked)狀態(tài),而執(zhí)行yield()方法后轉(zhuǎn)入就緒(ready)狀態(tài);
③ sleep()方法需要聲明拋出InterruptedException,而yield()方法沒有聲明任何異常;
④ sleep()方法比yield()方法(跟操作系統(tǒng)CPU調(diào)度相關(guān))具有更好的可移植性。

6. 線程類的一些常用方法: 

  • sleep(): 強(qiáng)迫一個(gè)線程睡眠N毫秒,是一個(gè)靜態(tài)方法,調(diào)用此方法要處理InterruptedException異常;

  • join():  讓一個(gè)線程等待另一個(gè)線程完成才繼續(xù)執(zhí)行;

  • yeild(): 線程讓步,暫停當(dāng)前正在執(zhí)行的線程對(duì)象讓出CPU資源,將當(dāng)前線程從運(yùn)行狀態(tài)轉(zhuǎn)換到就緒狀態(tài)并執(zhí)行其他優(yōu)先級(jí)相同或更高的線程;

  • isAlive(): 判斷一個(gè)線程是否存活。 

  • activeCount(): 程序中活躍的線程數(shù)。 

  • enumerate(): 枚舉程序中的線程。 

  • currentThread(): 得到當(dāng)前線程。 

  • isDaemon(): 一個(gè)線程是否為守護(hù)線程。 

  • setDaemon(): 設(shè)置一個(gè)線程為守護(hù)線程。(用戶線程和守護(hù)線程的區(qū)別在于,是否等待主線程依賴于主線程結(jié)束而結(jié)束) 

  • setName(): 為線程設(shè)置一個(gè)名稱。 

  • setPriority(): 設(shè)置一個(gè)線程的優(yōu)先級(jí)。

  • wait():使一個(gè)線程處于等待(阻塞)狀態(tài),并且釋放所持有的對(duì)象的鎖;

  • notify():?jiǎn)拘岩粋€(gè)處于等待狀態(tài)的線程,當(dāng)然在調(diào)用此方法的時(shí)候,并不能確切的喚醒某一個(gè)等待狀態(tài)的線程,而是由JVM確定喚醒哪個(gè)線程,而且與優(yōu)先級(jí)無關(guān);

  • notityAll():?jiǎn)拘阉刑幱诘却隣顟B(tài)的線程,該方法并不是將對(duì)象的鎖給所有線程,而是讓它們競(jìng)爭(zhēng),只有獲得鎖的線程才能進(jìn)入就緒狀態(tài);

7. 同步代碼塊和同步方法的區(qū)別

  兩者的區(qū)別主要體現(xiàn)在同步鎖上面。對(duì)于實(shí)例的同步方法,因?yàn)橹荒苁褂胻his來作為同步鎖,如果一個(gè)類中需要使用到多個(gè)鎖,為了避免鎖的沖突,必然需要使用不同的對(duì)象,這時(shí)候同步方法不能滿足需求,只能使用同步代碼塊(同步代碼塊可以傳入任意對(duì)象);或者多個(gè)類中需要使用到同一個(gè)鎖,這時(shí)候多個(gè)類的實(shí)例this顯然是不同的,也只能使用同步代碼塊,傳入同一個(gè)對(duì)象。

8. 對(duì)比synchronized和Lock

1)、synchronized是關(guān)鍵字,就和if...else...一樣,是語法層面的實(shí)現(xiàn),因此synchronized獲取鎖以及釋放鎖都是Java虛擬機(jī)幫助用戶完成的;ReentrantLock是類層面的實(shí)現(xiàn),因此鎖的獲取以及鎖的釋放都需要用戶自己去操作。特別再次提醒,ReentrantLock在lock()完了,一定要手動(dòng)unlock(),一般放在finally語句塊中。

2)、synchronized簡(jiǎn)單,簡(jiǎn)單意味著不靈活,而ReentrantLock的鎖機(jī)制給用戶的使用提供了極大的靈活性。這點(diǎn)在Hashtable和ConcurrentHashMap中體現(xiàn)得淋漓盡致。synchronized一鎖就鎖整個(gè)Hash表,而ConcurrentHashMap則利用ReentrantLock實(shí)現(xiàn)了鎖分離,鎖的只是segment而不是整個(gè)Hash表

3)、synchronized是不公平鎖,而ReentrantLock可以指定鎖是公平的還是非公平的

4)、synchronized實(shí)現(xiàn)等待/通知機(jī)制通知的線程是隨機(jī)的,ReentrantLock實(shí)現(xiàn)等待/通知機(jī)制可以有選擇性地通知

5)、和synchronized相比,ReentrantLock提供給用戶多種方法用于鎖信息的獲取,比如可以知道lock是否被當(dāng)前線程獲取、lock被同一個(gè)線程調(diào)用了幾次、lock是否被任意線程獲取等等

總結(jié)起來,我認(rèn)為如果只需要鎖定簡(jiǎn)單的方法、簡(jiǎn)單的代碼塊,那么考慮使用synchronized,復(fù)雜的多線程處理場(chǎng)景下可以考慮使用ReentrantLock。

 

Voiatile關(guān)鍵字:

volatile關(guān)鍵字是Java并發(fā)的最輕量級(jí)實(shí)現(xiàn),本質(zhì)上有兩個(gè)功能,在生成的匯編語句中加入LOCK關(guān)鍵字和內(nèi)存屏障

作用就是保證每一次線程load和write兩個(gè)操作,都會(huì)直接從主內(nèi)存中進(jìn)行讀取和覆蓋,而非普通變量從線程內(nèi)的工作空間(默認(rèn)各位已經(jīng)熟悉Java多線程內(nèi)存模型)

但它有一個(gè)很致命的缺點(diǎn),導(dǎo)致它的使用范圍不多,就是他只保證在讀取和寫入這兩個(gè)過程是線程安全的。如果我們對(duì)一個(gè)volatile修飾的變量進(jìn)行多線程 下的自增操作,還是會(huì)出現(xiàn)線程安全問題。根本原因在于volatile關(guān)鍵字無法對(duì)自增進(jìn)行安全性修飾,因?yàn)樽栽龇譃槿?,讀取-》+1-》寫入。中間多 個(gè)線程同時(shí)執(zhí)行+1操作,還是會(huì)出現(xiàn)線程安全性問題。

上一篇博文:Java多線程(一) —— 線程的狀態(tài)詳解中詳細(xì)介紹了線程的五種狀態(tài)及狀態(tài)間的轉(zhuǎn)換。本文著重介紹了線程安全的相關(guān)知識(shí)點(diǎn),包括線程同步和鎖機(jī)制、線程間通信以及相關(guān)面試題的總結(jié)

一、線程安全

多個(gè)線程在執(zhí)行同一段代碼的時(shí)候,每次的執(zhí)行結(jié)果和單線程執(zhí)行的結(jié)果都是一樣的,不存在執(zhí)行結(jié)果的二義性,就可以稱作是線程安全的。

講到線程安全問題,其實(shí)是指多線程環(huán)境下對(duì)共享資源的訪問可能會(huì)引起此共享資源的不一致性。因此,為避免線程安全問題,應(yīng)該避免多線程環(huán)境下對(duì)此共享資源的并發(fā)訪問。

線程安全問題多是由全局變量和靜態(tài)變量引起的,當(dāng)多個(gè)線程對(duì)共享數(shù)據(jù)只執(zhí)行讀操作,不執(zhí)行寫操作時(shí),一般是線程安全的;當(dāng)多個(gè)線程都執(zhí)行寫操作時(shí),需要考慮線程同步來解決線程安全問題。

 

二、線程同步(synchronized/Lock)

線程同步:將操作共享數(shù)據(jù)的代碼行作為一個(gè)整體,同一時(shí)間只允許一個(gè)線程執(zhí)行,執(zhí)行過程中其他線程不能參與執(zhí)行。目的是為了防止多個(gè)線程訪問一個(gè)數(shù)據(jù)對(duì)象時(shí),對(duì)數(shù)據(jù)造成的破壞。

(1)同步方法(synchronized)

對(duì)共享資源進(jìn)行訪問的方法定義中加上synchronized關(guān)鍵字修飾,使得此方法稱為同步方法??梢院?jiǎn)單理解成對(duì)此方法進(jìn)行了加鎖,其鎖對(duì)象為當(dāng)前方法所在的對(duì)象自身。多線程環(huán)境下,當(dāng)執(zhí)行此方法時(shí),首先都要獲得此同步鎖(且同時(shí)最多只有一個(gè)線程能夠獲得),只有當(dāng)線程執(zhí)行完此同步方法后,才會(huì)釋放鎖對(duì)象,其他的線程才有可能獲取此同步鎖,以此類推...格式如下:

public synchronized void run() {        
     // ....}

 

(2)同步代碼塊(synchronized)

使用同步方法時(shí),使得整個(gè)方法體都成為了同步執(zhí)行狀態(tài),會(huì)使得可能出現(xiàn)同步范圍過大的情況,于是,針對(duì)需要同步的代碼可以直接另一種同步方式——同步代碼塊來解決。格式如下:

synchronized (obj) {        
     // ....}

其中,obj為鎖對(duì)象,因此,選擇哪一個(gè)對(duì)象作為鎖是至關(guān)重要的。一般情況下,都是選擇此共享資源對(duì)象作為鎖對(duì)象。

 

(3)同步鎖(Lock)

 使用Lock對(duì)象同步鎖可以方便地解決選擇鎖對(duì)象的問題,唯一需要注意的一點(diǎn)是Lock對(duì)象需要與資源對(duì)象同樣具有一對(duì)一的關(guān)系。Lock對(duì)象同步鎖一般格式為:

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

class X {    // 顯示定義Lock同步鎖對(duì)象,此對(duì)象與共享資源具有一對(duì)一關(guān)系
    private final Lock lock = new ReentrantLock();    
    public void m(){        // 加鎖        lock.lock();        
        //...  需要進(jìn)行線程安全同步的代碼        
        // 釋放Lock鎖        lock.unlock();
    }
}

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

 

什么時(shí)候需要同步:

(1)可見性同步:在以下情況中必須同步: 1)讀取上一次可能是由另一個(gè)線程寫入的變量 ;2)寫入下一次可能由另一個(gè)線程讀取的變量

(2)一致性同步:當(dāng)修改多個(gè)相關(guān)值時(shí),您想要其它線程原子地看到這組更改—— 要么看到全部更改,要么什么也看不到。

這適用于相關(guān)數(shù)據(jù)項(xiàng)(如粒子的位置和速率)和元數(shù)據(jù)項(xiàng)(如鏈表中包含的數(shù)據(jù)值和列表自身中的數(shù)據(jù)項(xiàng)的鏈)。

在某些情況中,您不必用同步來將數(shù)據(jù)從一個(gè)線程傳遞到另一個(gè),因?yàn)?JVM 已經(jīng)隱含地為您執(zhí)行同步。這些情況包括:

  1. 由靜態(tài)初始化器(在靜態(tài)字段上或 static{} 塊中的初始化器)

  2. 初始化數(shù)據(jù)時(shí) 

  3. 訪問 final 字段時(shí)

http://www.cnblogs.com/IUbanana/p/7112296.html