本文對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