java synchronized 同步機制解析

本文對synchronized的用法做了詳細介紹,並分別給出不同情況下的例子,感謝原作者fireshort,轉自http://www.jianshu.com/p/5f34a7f95626#


synchronized關鍵字可以作爲方法的修飾符(同步方法),也可作用於函數內的語句(同步代碼塊)。

掌握synchronized,關鍵是要掌握把那個東西作爲鎖。對於類的非靜態方法(成員方法)而言,意味着要取得對象實例的鎖;對於類的靜態方法(類方法)而言,要取得類的Class對象的鎖;對於同步代碼塊,要指定取得的是哪個對象的鎖。同步非靜態方法可以視爲包含整個方法的synchronized(this) { … }代碼塊。   

不管是同步代碼塊還是同步方法,每次只有一個線程可以進入(在同一時刻最多隻有一個線程執行該段代碼。),如果其他線程試圖進入(不管是同一同步塊還是不同的同步塊),jvm會將它們掛起(放入到等鎖池中)。這種結構在併發理論中稱爲臨界區(critical section)。

在jvm內部,爲了提高效率,同時運行的每個線程都會有它正在處理的數據的緩存副本,當我們使用synchronzied進行同步的時候,真正被同步的是在不同線程中表示被鎖定對象的內存塊(副本數據會保持和主內存的同步,現在知道爲什麼要用同步這個詞彙了吧),簡單的說就是在同步塊或同步方法執行完後,對被鎖定的對象做的任何修改要在釋放鎖之前寫回到主內存中;在進入同步塊得到鎖之後,被鎖定對象的數據是從主內存中讀出來的,持有鎖的線程的數據副本一定和主內存中的數據視圖是同步的 。

下面舉具體的例子來說明synchronized的各種情況。

兩個線程同時訪問一個對象的同步方法

當兩個併發線程訪問同一個對象的同步方法時,只能有一個線程得到執行。另一個線程必須等待當前線程執行完這個以後才能執行。

public class TwoThread {
    public static void main(String[] args) {
        final TwoThread twoThread = new TwoThread();

        Thread t1 = new Thread(new Runnable() {
            public void run() {
                twoThread.syncMethod();
            }
        }, "A");
        Thread t2 = new Thread(new Runnable() {
            public void run() {
                twoThread.syncMethod();
            }
        }, "B");

        t1.start();
        t2.start();
    }

    public synchronized void syncMethod() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " : " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException ie) {
            }
        }
    }

}

輸出結果:

A : 0
A : 1
A : 2
A : 3
A : 4
B : 0
B : 1
B : 2
B : 3
B : 4

兩個線程訪問的是兩個對象的同步方法

這種情況下,synchronized不起作用,跟普通的方法一樣。因爲對應的鎖是各自的對象。

public class TwoObject {
    public static void main(String[] args) {
        final TwoObject object1 = new TwoObject();
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                object1.syncMethod();
            }
        }, "Object1");
        t1.start();

        final TwoObject object2 = new TwoObject();
        Thread t2 = new Thread(new Runnable() {
            public void run() {
                object2.syncMethod();
            }
        }, "Object2");
        t2.start();
    }

    public synchronized void syncMethod() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " : " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException ie) {
            }
        }
    }

}

其中一種可能的輸出結果:

Object2 : 0
Object1 : 0
Object1 : 1
Object2 : 1
Object2 : 2
Object1 : 2
Object2 : 3
Object1 : 3
Object1 : 4
Object2 : 4

兩個線程訪問的是synchronized的靜態方法

這種情況,由於鎖住的是Class,在任何時候,該靜態方法只有一個線程可以執行。

同時訪問同步方法與非同步方法

當一個線程訪問對象的一個同步方法時,另一個線程仍然可以訪問該對象中的非同步方法。

public class SyncAndNoSync {
    public static void main(String[] args) {
        final SyncAndNoSync syncAndNoSync = new SyncAndNoSync();

        Thread t1 = new Thread(new Runnable() {
            public void run() {
                syncAndNoSync.syncMethod();
            }
        }, "A");
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            public void run() {
                syncAndNoSync.noSyncMethod();
            }
        }, "B");
        t2.start();
    }

    public synchronized void syncMethod() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " at syncMethod(): " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException ie) {
            }
        }
    }

    public void noSyncMethod() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " at noSyncMethod(): " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException ie) {
            }
        }
    }

}

一種可能的輸出結果:

B at noSyncMethod(): 0
A at syncMethod(): 0
B at noSyncMethod(): 1
A at syncMethod(): 1
B at noSyncMethod(): 2
A at syncMethod(): 2
B at noSyncMethod(): 3
A at syncMethod(): 3
A at syncMethod(): 4
B at noSyncMethod(): 4

訪問同一個對象的不同同步方法

當一個線程訪問一個對象的同步方法A時,其他線程對該對象中所有其它同步方法的訪問將被阻塞。因爲第一個線程已經獲得了對象鎖,其他線程得不到鎖,則雖然是訪問不同的方法,但是沒有獲得鎖,也無法訪問。

public class TwoSyncMethod {
    public static void main(String[] args) {
        final TwoSyncMethod twoSyncMethod = new TwoSyncMethod();

        Thread t1 = new Thread(new Runnable() {
            public void run() {
                twoSyncMethod.syncMethod1();
            }
        }, "A");
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            public void run() {
                twoSyncMethod.syncMethod2();
            }
        }, "B");
        t2.start();
    }

    public synchronized void syncMethod1() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " at syncMethod1(): " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException ie) {
            }
        }
    }

    public synchronized void syncMethod2() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " at syncMethod2(): " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException ie) {
            }
        }
    }

}

輸出結果:

A at syncMethod1(): 0
A at syncMethod1(): 1
A at syncMethod1(): 2
A at syncMethod1(): 3
A at syncMethod1(): 4
B at syncMethod2(): 0
B at syncMethod2(): 1
B at syncMethod2(): 2
B at syncMethod2(): 3
B at syncMethod2(): 4

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章