Java線程(二):線程同步synchronized和volatile

來源:互聯網
上載者:User

        上一篇:Java線程(一)

        上篇通過一個簡單的例子說明了安全執行緒與不安全,在例子中不安全的情況下輸出的結果恰好是逐個遞增的,為什麼會產生這樣的結果呢,因為建立的Count對象是線程共用的,一個線程改變了其成員變數num值,下一個線程正巧讀到了修改後的num,所以會遞增輸出。

        要說明線程同步問題首先要說明Java線程的兩個特性,可見度和有序性。多個線程之間是不能直接傳遞資料互動的,它們之間的互動只能通過共用變數來實現。拿上篇博文中的例子來說明,在多個線程之間共用了Count類的一個對象,這個對象是被建立在主記憶體(堆記憶體)中,每個線程都有自己的工作記憶體(線程棧),工作記憶體儲存了主記憶體Count對象的一個副本,當線程操作Count對象時,首先從主記憶體複製Count對象到工作記憶體中,然後執行代碼count.count(),改變了num值,最後用工作記憶體Count重新整理主記憶體Count。當一個對象在多個記憶體中都存在副本時,如果一個記憶體修改了共用變數,其它線程也應該能夠看到被修改後的值,此為可見度。由上述可知,一個運算賦值操作並不是一個原子性操作,多個線程執行時,CPU對線程的調度是隨機的,我們不知道當前程式被執行到哪步就切換到了下一個線程,一個最經典的例子就是銀行匯款問題,一個銀行賬戶存款100,這時一個人從該賬戶取10元,同時另一個人向該賬戶匯10元,那麼餘額應該還是100。那麼此時可能發生這種情況,A線程負責取款,B線程負責匯款,A從主記憶體讀到100,B從主記憶體讀到100,A執行減10操作,並將資料重新整理到主記憶體,這時主記憶體資料100-10=90,而B記憶體執行加10操作,並將資料重新整理到主記憶體,最後主記憶體資料100+10=110,顯然這是一個嚴重的問題,我們要保證A線程和B線程有序執行,先取款後匯款或者先匯款後取款,此為有序性

        下面同樣用代碼來展示一下線程同步問題。

        TraditionalThreadSynchronized.java:建立兩個線程,執行同一個對象的輸出方法。

public class TraditionalThreadSynchronized {public static void main(String[] args) {final Outputter output = new Outputter();new Thread() {public void run() {output.output("zhangsan");};}.start();new Thread() {public void run() {output.output("lisi");};}.start();}}class Outputter {public void output(String name) {// TODO 為了保證對name的輸出不是一個原子操作,這裡逐個輸出name的每個字元for(int i = 0; i < name.length(); i++) {System.out.print(name.charAt(i));}}}

        運行結果:

zhlainsigsan

        顯然輸出的字串被打亂了,我們期望的輸出結果是zhangsanlisi,這就是線程同步問題,我們希望output方法被一個線程完整的執行完之後在切換到下一個線程,Java中使用synchronized保證一段代碼在多線程執行時是互斥的,有兩種用法:

        1. 使用synchronized將需要互斥的程式碼封裝含起來,並上一把鎖。

synchronized (this) {    for(int i = 0; i < name.length(); i++) {        System.out.print(name.charAt(i));    }}

        這把鎖必須是線程間的共用對象,像下面的代碼是沒有意義的。

Object lock = new Object();synchronized (lock) {    for(int i = 0; i < name.length(); i++) {        System.out.print(name.charAt(i));    }}

        每次進入output方法都會建立一個新的lock,這個鎖顯然每個線程都會建立,沒有意義。

        2. 將synchronized加在需要互斥的方法上。

public synchronized void output(String name) {    // TODO 線程輸出方法    for(int i = 0; i < name.length(); i++) {        System.out.print(name.charAt(i));    }}

        這種方式就相當於用this鎖住整個方法內的代碼塊,如果用synchronized加在靜態方法上,就相當於用××××.class鎖住整個方法內的代碼塊。使用synchronized在某些情況下會造成死結,死結問題以後會說明。

        每個鎖對象都有兩個隊列,一個是就緒隊列,一個是阻塞隊列,就緒佇列儲存體了將要獲得鎖的線程,阻塞佇列儲存體了被阻塞的線程,當一個線程被喚醒(notify)後,才會進入到就緒隊列,等待CPU的調度,反之,當一個線程被wait後,就會進入阻塞隊列,等待下一次被喚醒,這個涉及到線程間的通訊,下一篇博文會說明。看我們的例子,當第一個線程執行輸出方法時,獲得同步鎖,執行輸出方法,恰好此時第二個線程也要執行輸出方法,但發現同步鎖沒有被釋放,第二個線程就會進入就緒隊列,等待鎖被釋放。一個線程執行互斥代碼過程如下:

        1. 獲得同步鎖;

        2. 清空工作記憶體;

        3. 從主記憶體拷貝對象副本到工作記憶體;

        4. 執行代碼(計算或者輸出等);

        5. 重新整理主記憶體資料;

        6. 釋放同步鎖。

        所以,synchronized既保證了多線程的並發有序性,又保證了多線程的記憶體可見度。

        volatile是第二種Java多線程同步的手段,根據JLS的說法,一個變數可以被volatile修飾,在這種情況下記憶體模型確保所有線程可以看到一致的變數值,來看一段代碼:

class Test {static int i = 0, j = 0;static void one() {i++;j++;}static void two() {System.out.println("i=" + i + " j=" + j);}}

        一些線程執行one方法,另一些線程執行two方法,two方法有可能列印出j比i大的值,按照之前分析的線程執行過程分析一下:

        1. 將變數i從主記憶體拷貝到工作記憶體;

        2. 改變i的值;

        3. 重新整理主記憶體資料;

        4. 將變數j從主記憶體拷貝到工作記憶體;

        5. 改變j的值;

        6. 重新整理主記憶體資料;

        這個時候執行two方法的線程先讀取了主存i原來的值又讀取了j改變後的值,這就導致了程式的輸出不是我們預期的結果,那麼可以在共用變數之前加上volatile。

class Test {static volatile int i = 0, j = 0;static void one() {i++;j++;}static void two() {System.out.println("i=" + i + " j=" + j);}}

        加上volatile可以將共用變數i和j的改變直接響應到主記憶體中,這樣保證了i和j的值可以保持一致,然而我們不能保證執行two方法的線程是在i和j執行到什麼程度擷取到的,所以volatile可以保證記憶體可見度,不能保證並發有序性。           

        下一篇:Java線程(三)

        本文來自:高爽|Coder,原文地址:http://blog.csdn.net/ghsau/article/details/7424694。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.