synchronized
是Java
中的同步關鍵字。
如果一個對象可能被多個線程同時訪問,我們將這個資源稱之為臨界資源。當多個線程同時訪問一個資源時,可能出現數據不一致或不完整的情況。所以需要采取同步措施,保證同一時間內只有一個線程訪問該資源。
synchronized
修飾符實現的同步機制叫互斥鎖機制,它所獲得的鎖叫做互斥鎖,每個對象都有一個鎖標記。
synchronized
可以修飾方法,代碼塊。
修飾代碼塊
看一個栗子:
/**
* @author zhaojiabao 2017/8/31
*/
public class TestSynchronized {
private void func1() {
synchronized (this) {
for (int i = 0; i < 10; i++) {
System.out.println("func1: " + Thread.currentThread());
}
}
}
public static void main(String[] args) {
TestSynchronized obj1 = new TestSynchronized();
new Thread(obj1::func1).start();
new Thread(obj1::func1).start();
}
}
輸出:
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
可以看到,雖然Thread1
和Thread2
是并發執行的,但等到Thread1
執行完畢后Thread2
才開始執行。
是因為這兩個線程同時訪問了同一個對象的synchronized
代碼塊。所以Thread2
的訪問被阻塞了。直到Thread1
訪問完畢,Thread2
才可以訪問synchronized
代碼塊。
注意這里鎖定的是this
:
synchronized (this)
規則是這樣的:
則當一個線程在訪問某個對象的synchronized (this)
代碼塊時,其他試圖訪問這個對象中所有被
synchronized (this)
修飾的代碼塊的線程都會被阻塞。
也可以用一個特定的對象來作為線程鎖:
Object syncObject = new Object();
synchronized (syncObject) {
//TODO
}
this
也是一個對象,本質上是一樣的。
修飾方法
當一個方法被synchronized
修飾時,線程想要執行該方法,必須拿到這個方法所屬對象的鎖。
看一個例子:
/**
* @author zhaojiabao 2017/8/31
*/
public class TestSynchronized {
private synchronized void func2() {
for (int i = 0; i < 10; i++) {
System.out.println("func2: " + Thread.currentThread());
}
}
private void func1() {
synchronized (this) {
for (int i = 0; i < 10; i++) {
System.out.println("func1: " + Thread.currentThread());
}
}
}
public static void main(String[] args) {
TestSynchronized obj1 = new TestSynchronized();
new Thread(obj1::func2).start();
new Thread(obj1::func1).start();
}
}
輸出:
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
可以看到,由于Thread1
拿到了obj1
對象鎖,故Thread2
的執行被阻塞, 直到Thread1
執行完畢后Thread2
才開始執行。
類鎖
類鎖的兩種寫法:
synchronized (Foo.class) {
}
2.同步的static方法:
synchronized static void foo() {
}
類鎖是用來控制靜態方法(或靜態變量互斥體)之間的同步。
對象鎖是用來控制實例方法之間的同步。
這就是區別。
PS:
其實我感覺第一種和第二種寫法其實是不等價的。因為靜態方法只能訪問類的靜態變量,所以第一種同步的其實是類的靜態變量;而第二種方法同步是可以訪問類的靜態和非靜態變量,所以相當于將靜態/非靜態變量都同步了,綜上,兩種寫法其實應該是不等價的。
使用一個局部變量作為線程鎖
正常情況下,這種寫法是沒有意義的。
因為是局部變量,多線程是無法共享的。借用圣騎士Wind博客中的一句話:
如果一個變量是局部變量,那么每個線程都會有一個該局部變量的拷貝(即便是同一個對象中的方法的局部變量,也會對每一個線程有一個拷貝),一個線程對該局部變量的改變不會影響到其他線程。
既然局部變量不會被多個線程共享,那么多個線程拿到根本不是同一個對象,也就意味著不是同一把對象鎖。綜上,局部變量作為線程鎖是沒有意義的。
但是,某些時候你會發現,有人會把一個局部變量作為線程鎖,看下Stack Overflow上的這個問題:
use local variable as thread sync lock
對象鎖的選擇
這里稍微裝下逼,當我們需要一個對象鎖時,一般都會這樣寫:
Object mSyncObj = new Object();
咳咳,其實有一種逼格更高的寫法:
byte[] mSyncObj = new byte[0];
零長度的byte
數組對象創建起來將比任何對象都經濟――查看編譯后的字節碼:生成零長度的byte[]
對象只需3條操作碼,而
Object mSyncObj = new Object();
則需要7行操作碼。