一、概念
synchronized 是 Java 中的關鍵字,是利用鎖的機制來實現(xiàn)同步的。
鎖機制有如下兩種特性:
互斥性:即在同一時間只允許一個線程持有某個對象鎖,通過這種特性來實現(xiàn)多線程中的協(xié)調(diào)機制,這樣在同一時間只有一個線程對需同步的代碼塊(復合操作)進行訪問?;コ庑晕覀円餐Q為操作的原子性。
可見性:必須確保在鎖被釋放之前,對共享變量所做的修改,對于隨后獲得該鎖的另一個線程是可見的(即在獲得鎖時應獲得最新共享變量的值),否則另一個線程可能是在本地緩存的某個副本上繼續(xù)操作從而引起不一致。
二、對象鎖和類鎖
1. 對象鎖
在 Java 中,每個對象都會有一個 monitor 對象,這個對象其實就是 Java 對象的鎖,通常會被稱為“內(nèi)置鎖”或“對象鎖”。類的對象可以有多個,所以每個對象有其獨立的對象鎖,互不干擾。
2. 類鎖
在 Java 中,針對每個類也有一個鎖,可以稱為“類鎖”,類鎖實際上是通過對象鎖實現(xiàn)的,即類的 Class 對象鎖。每個類只有一個 Class 對象,所以每個類只有一個類鎖。
三、synchronized 的用法分類
synchronized 的用法可以從兩個維度上面分類:
1. 根據(jù)修飾對象分類
synchronized 可以修飾方法和代碼塊
-
修飾代碼塊
synchronized(this|object) {}
synchronized(類.class) {}
-
修飾方法
修飾非靜態(tài)方法
修飾靜態(tài)方法
2. 根據(jù)獲取的鎖分類
-
獲取對象鎖
synchronized(this|object) {}
修飾非靜態(tài)方法
-
獲取類鎖
synchronized(類.class) {}
修飾靜態(tài)方法,非靜態(tài)方法
四、synchronized 的用法詳解
這里根據(jù)獲取的鎖分類來分析 synchronized 的用法
1、對象鎖
這個對象是新建的,跟其他對象沒有任何關系:
/** * synchronized 修飾非靜態(tài)方法 */
private void sync5() {
Log.d(TAG,Thread.currentThread().getName() + "_Sync5: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); **synchronized (new SyncThread())** { try {
Log.d(TAG, Thread.currentThread().getName() + "_Sync5_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(2000);
Log.d(TAG, Thread.currentThread().getName() + "_Sync5_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
文章后面所有例子都是采用這四個線程,只是對方法進行修改:
SyncThread syncThread = new SyncThread();
Thread F_thread1 = new Thread(new SyncThread(), "F_thread1");
Thread F_thread2 = new Thread(new SyncThread(), "F_thread2");
Thread F_thread3 = new Thread(syncThread, "F_thread3");
Thread F_thread4 = new Thread(syncThread, "F_thread4");
F_thread1.start();
F_thread2.start();
F_thread3.start();
F_thread4.start();
運行結果如下:

四個線程同時開始,同時結束,因為作為鎖的對象與線程是屬于不同的實例。
2、采用類鎖,無所謂哪個類,都會被攔截:
/** * synchronized 修飾非靜態(tài)方法 */
private void sync5() {
Log.d(TAG,Thread.currentThread().getName() + "_Sync5: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); **synchronized (MainActivity.class****)** { try {
Log.d(TAG, Thread.currentThread().getName() + "_Sync5_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(2000);
Log.d(TAG, Thread.currentThread().getName() + "_Sync5_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
運行結果如下:

可以發(fā)現(xiàn),采用類鎖一次只能通過一個。即使采用的是 **MainActivity.class **這個類鎖。
3、采用 this 對象鎖:
/** * synchronized 修飾非靜態(tài)方法 */
private void sync5() {
Log.d(TAG,Thread.currentThread().getName() + "_Sync5: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); **synchronized (this****)** { try {
Log.d(TAG, Thread.currentThread().getName() + "_Sync5_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(2000);
Log.d(TAG, Thread.currentThread().getName() + "_Sync5_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
運行結果如下:

可以發(fā)現(xiàn)線程1,2同時結束,3,4有先后,原因是3,4同屬于一個實例。
4、synchronized 修飾方法
作用范圍是整個方法,所以方法中所有的代碼都是同步的:
/** * synchronized 修飾非靜態(tài)方法 */
**private synchronized void** **sync5**() {
Log.d(TAG, Thread.currentThread().getName() + "_Sync5: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); try {
Log.d(TAG, Thread.currentThread().getName() + "_Sync5_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(2000);
Log.d(TAG, Thread.currentThread().getName() + "_Sync5_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
修飾非靜態(tài)方法:

對于非靜態(tài)方法,同一個實例的線程訪問會被攔截,非同一實例可以同時訪問。 即此時是默認對象鎖(this)。
修飾靜態(tài)方法結果

可以看出來,靜態(tài)方法默認類鎖。
總結
1、對于靜態(tài)方法,由于此時對象還未生成,所以只能采用類鎖;
2、只要采用類鎖,就會攔截所有線程,只能讓一個線程訪問。
3、對于對象鎖(this),如果是同一個實例,就會按順序訪問,但是如果是不同實例,就可以同時訪問。
4、如果對象鎖跟訪問的對象沒有關系,那么就會都同時訪問。