多線程之volatile與synchronized(二)

JMM中主要是圍繞并發(fā)過程中如何處理原子性,可見性和有序性三個特性來建立的。最終可以保證線程安全性尊浓,volatile和synchronized兩個關鍵字又是我們最常碰到與最容易提到的關鍵字荔泳,這次放在一起來講。

與文無關

線程安全性:當多個線程訪問某個類的時候占婉,不管運行環(huán)境采用何種調度方式或這些線程如何交替執(zhí)行,并且在主調代碼中不需要額外的同步或協(xié)同,這個類都能表現(xiàn)出正確的行為淫半,那么就稱這個類是線程安全的。

原子性匣砖、可見性與有序性

首先來看一下這幾個特性代表的具體含義科吭。

  • 原子性(Atomicity):原子性是指,一個操作是不可中斷的猴鲫。即使是多個線程一起執(zhí)行的時候对人,一個操作一旦開始,就不會被其他線程干擾拂共。

    JDK的包中提供了專門的原子包java.util.concurrent.atomic牺弄,synchronized關鍵字還有Lock來讓程序在并發(fā)環(huán)境下具有原子性的特點。

  • 可見性(Visibility):可見性是指當一個線程修改了共享變量的值宜狐,其它線程能立即得知這個修改势告。

    volatile,synchronized和final關鍵字能實現(xiàn)可見性。使用final關鍵字需要注意對象逃逸

  • 有序性:如果再本線程內觀察抚恒,所有操作都是有序的咱台,如果再一個線程中觀察另外一個線程,那么所有操作都是無序的俭驮。前半句是指“線程內表現(xiàn)為串行”回溺,后半句是指“指令重排序”現(xiàn)象和“工作內存與主內存同步延遲現(xiàn)象”

    volatile和synchronized關鍵字可以線程之間操作的有序性。

Volatile

一個變量定義為volatile之后,它將具有兩種特性:

  1. 保證次變了對所有線程的可見性馅而,一條線程修改了這個值祥诽,新值對其它線程是可以立即得知的。
  2. 禁止指令重排優(yōu)化瓮恭。

volatile變量在寫操作時候雄坪,會在寫操作后加上store屏障指令,將本地內存刷新到主內存屯蹦。
volatile變量讀操作的時候维哈,會在讀操作之前加入一條load屏障指令,從主內存中讀取共享變量登澜。

關于JMM的8大操作指令阔挠,可以查看我的上篇文章,java內存模型脑蠕。

volatile變量為什么在并發(fā)下不安全购撼?

volatile變量在各個線程的工作內存中也可以存在不一致的情況,但由于每次使用之前都要刷新谴仙,執(zhí)行引擎看不到不一致的情況迂求,因此可以認為不存在一致性問題,但是Java里面的運算并非原子操作晃跺。

假如說一個寫入值操作不需要依賴依賴這個值的原先值揩局,那么在進行寫入的時候我們就不需要進行讀取操作。
寫入操作對原本的值的時候沒有要求掀虎,那么所有線程都可以寫入新的值凌盯,雖然讀取到的值是相同的,每個線程的操作也是正確的烹玉,但是最終結果卻是錯誤的驰怎。


JMM

感興趣的可以運行如下代碼:

public class VolatileTest {
    public static volatile int count = 0;
    public static final int THREAD_COUNT = 20;

    public static void add(){
        count++;
    }

    public static void main(String[] args) {
        Thread[] threads = new Thread[THREAD_COUNT];
        for (int i = 0; i < THREAD_COUNT; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            });
            threads[i].start();
        }
        
        for (int i = 0; i < THREAD_COUNT; i++) {
            threads[i].join();
        }        
        
        System.out.println(count);
    }
    
}
// 如果并發(fā)正確的話:應該是20000,但是每次運行結果都不到20000
Volatile適合做什么春霍?

適合做標量砸西,當一個線程對某個變量進行讀寫操作,而其它線程僅僅進行讀操作的時候址儒,是可以保證volatile的正確性的。如下:

volatile bool stopped;
public void stop(){
    stopped = true
}

while(!stoppped){
    // 執(zhí)行操作
}

Synchronized

Synchronized保證了原子性衅疙,可見性與有序性莲趣,它的工作時對同步的代碼塊加鎖,使得每次只有一個線程進入代碼塊饱溢,從而保證線程安全喧伞。synchronized反應到字節(jié)碼層面就是monitorenter與monitorexit.

注意*:雖然synchonized關鍵字看起來是萬能的,能保證線程安全性,但是越萬能的控制往往越伴隨著越大的性能影響潘鲫。

Synchonzied用法
  1. 實例方法上翁逞,被修飾的方法稱為同步方法,其作用的范圍是整個方法溉仑,作用的對象是調用這個方法的對象挖函;
  2. 靜態(tài)方法上,其作用的范圍是整個靜態(tài)方法浊竟,作用的對象是這個類的所有對象怨喘;
  3. 實例方法代碼塊.
  4. 靜態(tài)方法代碼塊。
 //實例方法
 public synchronized void add(int value){
      this.count += value;
 }
 //靜態(tài)方法
 public static synchronized void add(int value){
      count += value;
 }
 
 //實例方法代碼塊 
 public void add(int value){
   synchronized(this){
       this.count += value;   
    }
 }
 
 //靜態(tài)方法代碼塊
 public class MyClass {

    public static synchronized void log1(String msg1, String msg2){
       log.writeln(msg1);
       log.writeln(msg2);
    }

  
    public static void log2(String msg1, String msg2){
       synchronized(MyClass.class){
          log.writeln(msg1);
          log.writeln(msg2);  
       }
    }
  }
  
Synchonzied案例
public class SynchronziedTest implements Runnable{
    static int i = 0;
    static int j = 0;
    static SynchronziedTest instance=  new SynchronziedTest();

    @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) {
            increase();
        }
    }

    public synchronized void increase(){
        i++;
    }

    public static void main(String[] args) throws InterruptedException {
        // 注意新建的線程指向的同一個實例振定,
        // 如果指向不同的實例必怜,那么兩個線程關注的鎖就不是同一把鎖,就會導致線程不安全
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        
        //錯誤的用法
//        Thread t3 = new Thread(new SynchronziedTest());
//        Thread t4 = new Thread(new SynchronziedTest());
        
        
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}
//結果為:200000

注意創(chuàng)建線程的時候指向同一個實例后频,才會鎖住相同的對象梳庆。

最后

這次我們講了線程安全性的基本原則,然后解釋了volatile和synchronized關鍵字卑惜,多線程中不得不掌握的關鍵字靠益。

參考

  • 《實戰(zhàn)Java高并發(fā)設計》
  • 《深入理解JVM虛擬機》
  • 《Java并發(fā)編程與高并發(fā)解決方案》
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市残揉,隨后出現(xiàn)的幾起案子胧后,更是在濱河造成了極大的恐慌,老刑警劉巖抱环,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壳快,死亡現(xiàn)場離奇詭異,居然都是意外死亡镇草,警方通過查閱死者的電腦和手機眶痰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來梯啤,“玉大人竖伯,你說我怎么就攤上這事∫蛴睿” “怎么了七婴?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長察滑。 經常有香客問我打厘,道長,這世上最難降的妖魔是什么贺辰? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任户盯,我火速辦了婚禮嵌施,結果婚禮上,老公的妹妹穿的比我還像新娘莽鸭。我一直安慰自己吗伤,他們只是感情好,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布硫眨。 她就那樣靜靜地躺著足淆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捺球。 梳的紋絲不亂的頭發(fā)上缸浦,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音氮兵,去河邊找鬼裂逐。 笑死,一個胖子當著我的面吹牛泣栈,可吹牛的內容都是我干的卜高。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼南片,長吁一口氣:“原來是場噩夢啊……” “哼掺涛!你這毒婦竟也來了?” 一聲冷哼從身側響起疼进,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤薪缆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后伞广,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拣帽,經...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年嚼锄,在試婚紗的時候發(fā)現(xiàn)自己被綠了减拭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡区丑,死狀恐怖拧粪,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情沧侥,我是刑警寧澤可霎,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站正什,受9級特大地震影響啥纸,放射性物質發(fā)生泄漏。R本人自食惡果不足惜婴氮,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一斯棒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧主经,春花似錦荣暮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至惠遏,卻和暖如春砾跃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背节吮。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工抽高, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人透绩。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓翘骂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親帚豪。 傳聞我的和親對象是個殘疾皇子碳竟,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內容