一、概念
synchronized 是 Java 中的關鍵字,是利用鎖的機制來實現同步的。
鎖機制有如下兩種特性:
-
互斥性:即在同一時間只允許一個線程持有某個對象鎖,通過這種特性來實現多線程中的協調機制,這樣在同一時間只有一個線程對需同步的代碼塊(復合操作)進行訪問。互斥性我們也往往稱為操作的原子性。
-
可見性:必須確保在鎖被釋放之前,對共享變量所做的修改,對於隨后獲得該鎖的另一個線程是可見的(即在獲得鎖時應獲得最新共享變量的值),否則另一個線程可能是在本地緩存的某個副本上繼續操作從而引起不一致。
二、對象鎖和類鎖
1. 對象鎖
在 Java 中,每個對象都會有一個 monitor 對象,這個對象其實就是 Java 對象的鎖,通常會被稱為“內置鎖”或“對象鎖”。類的對象可以有多個,所以每個對象有其獨立的對象鎖,互不干擾。
2. 類鎖
在 Java 中,針對每個類也有一個鎖,可以稱為“類鎖”,類鎖實際上是通過對象鎖實現的,即類的 Class 對象鎖。每個類只有一個 Class 對象,所以每個類只有一個類鎖。
三、synchronized 的用法分類
synchronized 的用法可以從兩個維度上面分類:
1. 根據修飾對象分類
synchronized 可以修飾方法和代碼塊
-
修飾代碼塊
-
synchronized(this|object) {}
-
synchronized(類.class) {}
-
-
修飾方法
-
修飾非靜態方法
-
修飾靜態方法
-
2. 根據獲取的鎖分類
-
獲取對象鎖
-
synchronized(this|object) {}
-
修飾非靜態方法
-
-
獲取類鎖
-
synchronized(類.class) {}
-
修飾靜態方法,非靜態方法
-
四、synchronized 的用法詳解
這里根據獲取的鎖分類來分析 synchronized 的用法
1、對象鎖
這個對象是新建的,跟其他對象沒有任何關系:
/** * synchronized 修飾非靜態方法 */ 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 修飾非靜態方法 */ 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(); } } }
運行結果如下:
可以發現,采用類鎖一次只能通過一個。即使采用的是 MainActivity.class 這個類鎖。
3、采用 this 對象鎖:
/** * synchronized 修飾非靜態方法 */ 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(); } } }
運行結果如下:
可以發現線程1,2同時結束,3,4有先后,原因是3,4同屬於一個實例。
4、synchronized 修飾方法
作用范圍是整個方法,所以方法中所有的代碼都是同步的:
/** * synchronized 修飾非靜態方法 */ 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(); } }
修飾非靜態方法:
對於非靜態方法,同一個實例的線程訪問會被攔截,非同一實例可以同時訪問。 即此時是默認對象鎖(this)。
修飾靜態方法結果
可以看出來,靜態方法默認類鎖。
總結
1、對於靜態方法,由於此時對象還未生成,所以只能采用類鎖;
2、只要采用類鎖,就會攔截所有線程,只能讓一個線程訪問。
3、對於對象鎖(this),如果是同一個實例,就會按順序訪問,但是如果是不同實例,就可以同時訪問。
4、如果對象鎖跟訪問的對象沒有關系,那么就會都同時訪問。