Java多線程:線程間通信之volatile與sychronized

由前文Java內(nèi)存模型我們熟悉了Java的內(nèi)存工作模式和線程間的交互規(guī)范滤否,本篇從應(yīng)用層面講解Java線程間通信导饲。

Java為線程間通信提供了三個相關(guān)的關(guān)鍵字volatile, synchronized和final。對于final,我們在博文Java中static關(guān)鍵字和final關(guān)鍵字中已經(jīng)介紹。

1. volatile

1.1. 定義

由volatile定義的變量其特殊性在于:

一個線程對變量的寫一定對之后對這個變量的讀的線程可見虱颗。

換言之

一個線程對volatile變量的讀一定能看見它之前最后一個線程對這個變量的寫。

1.2. 機理

volatile意味著可見性蔗喂,在講解volatile的機理前忘渔,我先給下面的這個例子:

package com.cielo.main;

/**
 * Created by 63289 on 2017/3/31.
 */
class MyThread extends Thread {
    private boolean isRunning = true;
    public boolean isRunning() {
        return isRunning;
    }
    public void setRunning(boolean isRunning) {
        this.isRunning = isRunning;
    }
    @Override
    public void run() {
        System.out.println("進入到run方法中了");
        while (isRunning == true) {
        }
        System.out.println("線程執(zhí)行完成了");
    }
}
public class RunThread{
    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.start();
            Thread.sleep(1000);
            thread.setRunning(false);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在這個例子中,主線程啟動了子線程缰儿,子線程成功進入run方法畦粮,輸出"進入到run方法中",只有由于isRunning==true,無限循環(huán)宣赔。此時预麸,sleep一秒后的主線程想要改變isRunning的值,它將isRunning變量讀取到它的內(nèi)存空間進行修改后儒将,寫入主內(nèi)存吏祸,但由于子線程一直在私有棧中讀取isRunning變量,沒有在主內(nèi)存中讀取isRunning變量钩蚊,因此不會退出循環(huán)贡翘。

如果我們把isRunning賦值行改為:

private volatile boolean isRunning = true;
將其用volatile修飾,則強制該變量從主內(nèi)存中讀取砰逻。

這樣我們也就明白了volatile的實現(xiàn)機理鸣驱,即:

  1. 當(dāng)一個線程要使用volatile變量時,它會直接從主內(nèi)存中讀取蝠咆,而不使用自己工作內(nèi)存中的副本踊东。

  2. 當(dāng)一個線程對一個volatile變量寫時,它會將變量的值刷新到共享內(nèi)存(主內(nèi)存)中刚操。

1.3. 特性:不會被重排序

從Java內(nèi)存模型一篇中闸翅,我們簡單了解了重排序,這里不會被重排序主要指語句重排序菊霜。

我們考慮到下面這個例子缎脾,有A,B兩個線程

線程A:加載配置文件,將配置元素初始化占卧,之后標(biāo)識初始化成功。

Map configOptions ;
char[] configText;

volatile boolean initialized = false;

//線程A首先從文件中讀取配置信息,調(diào)用process...處理配置信息,處理完成了將initialized 設(shè)置為true
configOptions = new HashMap();
configText = readConfigFile(fileName);
processConfig(configText, configOptions);//負責(zé)將配置信息configOptions 成功初始化
initialized = true;
線程B:等待初始化標(biāo)識為true联喘,之后開始工作华蜒。

while(!initialized)
{
    sleep();
}

//使用配置信息干活
doSomethingWithConfig();

很簡單的一個例子,在編譯器中豁遭,如果進行重排序叭喜,則會有將initialized=true這一行先執(zhí)行的可能,如果這件事發(fā)生的話蓖谢,線程B就會先運行捂蕴,進而使用了沒有加載配置文件的Object。而如果initialized變量使用了volatile修飾闪幽,則編譯器不會將該變量的相關(guān)代碼進行重排序啥辨。(當(dāng)然,這里的例子只是為了直觀盯腌,實際情況編譯器的重排序會更加復(fù)雜)

1.4. 非原子性

使用volatile時溉知,我們要清楚,volatile是非原子性的。

原子性即是指级乍,對于一個操作舌劳,其操作的內(nèi)容只有全部執(zhí)行/全不執(zhí)行兩個狀態(tài),不存在中間態(tài)玫荣。而volatile并不能鎖定某組操作甚淡,防止其他線程的干擾,即沒有規(guī)定原子性捅厂,因而volatile是非原子性的贯卦。或者說恒傻,volatile是非線程安全的脸侥。

綜上,如果我們想要使用一個原子性的修飾符來控制操作盈厘,即在操作變量時鎖定變量睁枕,我們就需要另一個修飾詞synchronized。

2. synchronized

2.1. 定義

synchronized作用的代碼范圍對于不同線程是互斥的沸手,并且線程在釋放鎖的時候會將共享變量的值刷新到共享內(nèi)存中外遇。

2.2. synchronized與voliatile區(qū)別

  1. 使用:voliatile 用于修飾變量,synchronized可以修飾對象契吉,類跳仿,方法,代碼塊捐晶,語句菲语。

  2. 原子性:voliatile只保證變量的可見性,不能用于同步變量惑灵,即不保證原子性山上,多線程并發(fā)訪問voliatile修飾的變量時也不會產(chǎn)生阻塞。synchronized是原子性的英支,只有鎖定了變量的線程才能進入臨界區(qū)佩憾,從而保證臨界區(qū)的所有語句全部執(zhí)行。多線程并發(fā)訪問sychronized修飾的變量會產(chǎn)生阻塞干花。

  3. 機理:

當(dāng)線程對volatile變量讀時妄帘,會把工作內(nèi)存中值置為無效。當(dāng)線程對sychronized變量讀時池凄,會在該線程鎖定變量時把工作內(nèi)存中值置為無效抡驼。

當(dāng)線程對voliatile變量寫時,會把值刷新到主內(nèi)存中肿仑。當(dāng)線程對sychronized變量寫時婶恼,會在變量解鎖時把值刷新到主內(nèi)存中桑阶。

2.3. 注意

  1. 無論synchronized加在方法上還是對象上,其修飾的都是對象勾邦,而不是方法或者某個代碼塊代碼語句蚣录。

  2. 每個對象只有一個鎖與之相關(guān)聯(lián)。

  3. 實現(xiàn)同步需要很大的系統(tǒng)開銷來做控制眷篇,不要做無謂的鎖定萎河。

2.4. synchronized的作用域

synchronized的作用域只有兩種。實際上蕉饼,synchronized直接作用于內(nèi)存中的一個內(nèi)存塊虐杯,因此,可以通過鎖定內(nèi)存塊來鎖定一個實例變量或者鎖定一個靜態(tài)區(qū)域昧港。

  1. 某個對象實例內(nèi)

synchronized aMethod(){}可以防止多個線程同時訪問這個對象的synchronized方法擎椰,如果對象有多個synchronized方法,則只要一個線程訪問了任何一個synchronized方法创肥,其他線程不能同時訪問任何一個該對象的synchronized方法(synchronized作用于對象达舒,且每個對象只有一個鎖)。

顯然叹侄,不同對象的synchronized方法則不會互相影響(synchronized作用于對象)巩搏。

  1. 某個類的范圍

又或者說作用于靜態(tài)方法/靜態(tài)代碼塊。synchronized static aMethod(){}防止多個線程同時訪問這個類中的synchronized static方法趾代,它可以對類的所有實例對象起作用贯底。

2.5. synchronized應(yīng)用

2.5.1. synchronized方法

每個實例對應(yīng)一個lock,線程獲得該含有synchronized方法的實例的鎖才可以執(zhí)行,否則阻塞撒强。方法一旦執(zhí)行禽捆,則一直到方法返回才可以釋放鎖。此后被阻塞的線程才能獲得該鎖飘哨。對于一個實例胚想,其聲明為synchronized的方法顯然只有一個能處于執(zhí)行狀態(tài)。從而避免了類訪問變量的沖突杖玲。

synchronized同步的開銷很大,如果synchronized作用于一個比較大的方法上淘正,顯然是不合算的摆马。

2.5.2. synchronized代碼塊

synchronized代碼塊形式如下:

        synchronized (synchronizedObject){
            //Some thing
        }

代碼塊內(nèi)部代碼必須在獲得synchronizedObject的鎖時才能執(zhí)行。需要重點說的是synchronized(this)鸿吆,這也是比較常用的代碼塊囤采。

synchronized的效果類似于在方法前修飾,只是修飾的范圍縮小成代碼塊惩淳。兩個線程同時訪問一個變量時蕉毯,如果一個線程在執(zhí)行synchronized的代碼乓搬,那么該實例被鎖定,另一個線程如果要訪問該實例被synchronized作用的范圍代虾,則會被阻塞进肯。

此外,如果不使用this作為鎖棉磨,而是只是想讓一段代碼同步江掩,可以臨時創(chuàng)建如下鎖:

    private byte[] lock=new byte[0];

從操作碼上講,創(chuàng)建一個長度為0的數(shù)組對象是最經(jīng)濟的乘瓤,只需要3條操作碼环形。

2.5.3. synchronized靜態(tài)方法

synchronized修飾靜態(tài)方法時或者在普通方法中以類為對象如下形式:

class StaticSynchronized{
    public void aMethod{
        synchronized (StaticSynchronized.class){
            //Some thing
        }
    }
}

為synchronized靜態(tài)方法。

注意的是衙傀,對于同一個類抬吟,其static和實例方法如果都用synchronized修飾,其作用的必然不是同一個對象(顯然)统抬。

2.5.4. synchronized對象

比較簡單粗暴的實現(xiàn)方式无蜂,直接把對象鎖定,思路也很清晰座云。Java負責(zé)跟蹤被加鎖的對象氧腰,該鎖定對象的線程每次給對象加鎖時對象的計數(shù)器+1,每次解鎖時計數(shù)器-1妆偏,如果對象的計數(shù)器為0刃鳄,那么解除該線程的鎖定。

3. 參考文章

如何使用 volatile, synchronized, final 進行線程間通信

JAVA多線程之volatile 與 synchronized 的比較

Java synchronized詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末钱骂,一起剝皮案震驚了整個濱河市叔锐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌见秽,老刑警劉巖愉烙,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異解取,居然都是意外死亡步责,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門禀苦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蔓肯,“玉大人,你說我怎么就攤上這事振乏≌岚” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵慧邮,是天一觀的道長调限。 經(jīng)常有香客問我舟陆,道長,這世上最難降的妖魔是什么耻矮? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任秦躯,我火速辦了婚禮,結(jié)果婚禮上淘钟,老公的妹妹穿的比我還像新娘宦赠。我一直安慰自己,他們只是感情好米母,可當(dāng)我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布勾扭。 她就那樣靜靜地躺著,像睡著了一般铁瞒。 火紅的嫁衣襯著肌膚如雪妙色。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天慧耍,我揣著相機與錄音身辨,去河邊找鬼。 笑死芍碧,一個胖子當(dāng)著我的面吹牛煌珊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播泌豆,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼定庵,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了踪危?” 一聲冷哼從身側(cè)響起蔬浙,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎贞远,沒想到半個月后畴博,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡蓝仲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年俱病,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片袱结。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡亮隙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出擎勘,到底是詐尸還是另有隱情咱揍,我是刑警寧澤颖榜,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布棚饵,位于F島的核電站煤裙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏噪漾。R本人自食惡果不足惜硼砰,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望欣硼。 院中可真熱鬧题翰,春花似錦、人聲如沸诈胜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽焦匈。三九已至血公,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間缓熟,已是汗流浹背累魔。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留够滑,地道東北人垦写。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像彰触,于是被迫代替她去往敵國和親梯投。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 從三月份找實習(xí)到現(xiàn)在渴析,面了一些公司晚伙,掛了不少,但最終還是拿到小米俭茧、百度咆疗、阿里、京東母债、新浪午磁、CVTE、樂視家的研發(fā)崗...
    時芥藍閱讀 42,246評論 11 349
  • 前言 今天介紹下volatile關(guān)鍵字毡们,volatile這個關(guān)鍵字可能很多朋友都聽說過迅皇,或許也都用過。在Java ...
    嘟爺MD閱讀 1,292評論 7 27
  • Java8張圖 11衙熔、字符串不變性 12登颓、equals()方法、hashCode()方法的區(qū)別 13红氯、...
    Miley_MOJIE閱讀 3,704評論 0 11
  • 早上看到周口淮陽一中框咙,一同學(xué)與同學(xué)發(fā)生口角被罰站咕痛,從六樓跳下身亡。 我不知道他跳樓之前究竟發(fā)生了什么喇嘱,但他的死亡是...
    菏葉母親閱讀 404評論 0 3
  • 現(xiàn)在發(fā)現(xiàn)茉贡,只要有心,一句話者铜、一件事腔丧、一篇文章、一部電影都能讓自己學(xué)習(xí)和成長作烟。有時候愉粤,不經(jīng)意間,電影的一句旁白也...
    妮子還在閱讀 134評論 1 1