簡介
上一篇文章《Java中synchronized實現對象鎖的兩種方式及原理解析》中,介紹了方法鎖的兩種實現方式及Synchronized
的底層原理,本文將講解synchronized
的類鎖的兩種實現方式。
一.類鎖的定義
什麼是類鎖
類鎖指synchronize修飾的靜態方法或指定鎖爲class對象。
類鎖來自何處?
不同的線程,訪問使用類鎖的方法的時候,他們獲取到的“鎖”,其實是Class對象。因爲同一個類中有且只有一個Class對象,但同一個類中可以有很多個其他對象。此時,就出現了同一個類中多個對象對Class對象使用的競爭,類鎖則保證了在同一時間,只允許一個線程訪問被類鎖鎖住的方法。
類鎖有兩種實現方式:
synchronized
加在static
方法上(靜態方法鎖)。synchronized(*.class
)代碼塊。
接下來我們分別來看一下類鎖的兩種實現形式,以及它們之間的區別。
二.靜態方法鎖的方式實現類鎖
我們使用類鎖,應該保證在同一個類中,不同對象的線程,執行同一方法時,保證多個線程串行執行此方法。則說明我們的類鎖生效了,帶着這個預期我們來看下,通過靜態方法鎖實現多個線程串行執行目標方法。
完整的代碼實例:
public class ClassLock1 implements Runnable {
// 這兩個實例,聲明爲static,是因爲要在main方法中測試,與方法鎖無關
static ClassLock1 instance1 = new ClassLock1();
static ClassLock1 instance2 = new ClassLock1();
// 關鍵: static synchronized兩個關鍵字同時使用
private static synchronized void method() {
System.out.println("線程名:" + Thread.currentThread().getName() + ",運行開始");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程:" + Thread.currentThread().getName() + ",運行結束");
}
@Override
public void run() {
method();
}
public static void main(String[] args) {
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance2);
thread1.start();
thread2.start();
try {
//兩個線程必須都執行完畢後
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("兩個線程都已經執行完畢");
}
}
運行結果:
線程名:Thread-1,運行開始
線程:Thread-1,運行結束
線程名:Thread-0,運行開始
線程:Thread-0,運行結束
兩個線程都已經執行完畢
二.靜態方法鎖的實現原理:
當在靜態方法上,使用synchronized
關鍵字修飾時,就可以是想方法的同步了。但其實使用類鎖的第二個關鍵點在,不同實例的線程,調用類中的方法時,也就是上述代碼中,main方法中的兩個線程thread1
、thread2
分別對應的instance1
和instance2
。這個代碼的精髓在於,同一個類中不同的兩個對象(instance1
和instance2
)的兩個線程,要控制這兩個線程串行執行,則需要使用類鎖。那大家可能有下面的疑問:爲什麼要用類鎖,用對象鎖會怎樣?
爲什麼要用類鎖,用對象鎖會怎樣?
先解釋如果不加static
,類鎖會變成什麼?如果看過前一篇文章《Java中synchronized實現對象鎖的兩種方式及原理解析》,你會知道:
synchronized能夠保證在同一時刻最多隻有一個線程執行該段代碼,以保證併發安全。
但只用的synchronized
修飾的方法其實是方法鎖,也就是對象鎖,對象鎖的作用範圍僅限於同一個對象內
。而上述代碼中,我們有兩個不同的對象(instance1
和instance2
),所以如果你把static
關鍵字去掉,則此同步方法就成了不同對象中的方法鎖,又因爲方法鎖是對象鎖中的一種實現形式,即此時instance1
和instance2
分別持有的是一把對象鎖。對象鎖的特性是,每個對象的對象鎖只能鎖住自己對象內的線程,而無法鎖住其他的對象中的線程,這就是爲何對象鎖不能鎖住不同對象中的線程的原因,這也是對象鎖和類鎖的區別。
方法鎖的作用範圍是,同一個對象內的不同線程,而類鎖則可以控制不同對象中的不同線程。
爲什麼同步方法上一定要加上static
才能實現類鎖?
同步方法加上static
關鍵字後,那此方法在class創建的時候,就已經初始化好了。類中所有的實例,同步使用這個方法,鎖的作用範圍是最大的。
如果不加static
關鍵字,則該方法會被instance1
和instance2
同時訪問到,因爲鎖的範圍僅僅侷限在instance1
和instance2
對象中。此時是不能實現instance1
和instance2
兩個對象所在的不同線程之間的同步。
三.synchronized(*.class)代碼塊方式實現類鎖
類鎖的第二種實現方式,它的語義是:
只要同步代碼塊中是*.class,不管有多少個對象調用這段代碼,都表示大家共用一個對象,這就實現了不同的實例,串行的執行此代碼塊。
完整代碼示例:
public class ClassLock2 implements Runnable {
static ClassLock2 instance1 = new ClassLock2();
static ClassLock2 instance2 = new ClassLock2();
@Override
public void run() {
method();
}
private void method() {
//關鍵:用*.class作爲鎖對象,即使在不同的實例中,也能保證線程安全
//對比:此形式類鎖,與對象鎖中的同步代碼塊鎖,只有鎖對象不同,同步代碼塊鎖中的鎖對象是this,而類鎖是*.class)
synchronized (ClassLock2.class) {
System.out.println("線程名:" + Thread.currentThread().getName() + ",運行開始");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程:" + Thread.currentThread().getName() + ",運行結束");
}
}
public static void main(String[] args) {
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance2);
thread1.start();
thread2.start();
try {
//兩個線程必須都執行完畢後
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("兩個線程都已經執行完畢");
}
}
運行結果
線程名:Thread-0,運行開始
線程:Thread-0,運行結束
線程名:Thread-1,運行開始
線程:Thread-1,運行結束
兩個線程都已經執行完畢
可以看出,兩個線程依然是串行執行,此種方式和上一種方式的結果一致。
爲什麼Java的synchronized
實現類鎖或對象鎖提供了兩種方式?
這兩種方式是:使用synchronized
修飾方法或使用synchronized
代碼塊。
提供兩種方式的原因是因爲synchronized
關鍵字的缺陷之一就是效率低。
synchronized只有當同步方法執行完或執行時拋出異常或調用wait系列方法時,纔會釋放鎖。若不釋放鎖,其他線程只能等待,所以相對相率比較低。
做任何事,提高效率的第一性原理是:
在滿足要求的情況下,儘可能的少做事
所以synchronized
關鍵字除了可以加在方法上之外,還提供了同步代碼塊,可以讓其在方法內部使用。我們在程序編寫時,應該在滿足要求的情況下,儘可能的減少synchronized
代碼塊內的代碼量,因爲synchronized
代碼塊內的代碼,都是串行執行的,效率自然不如多個線程並行處理效率高,所以應該將那些不需要同步的代碼移動到synchronized
代碼塊外執行,這樣的效率會更高。這就是synchronized
爲什麼提供兩種方式做同步。
四.總結
本文承接上文《Java中synchronized實現對象鎖的兩種方式及原理解析》講解完對象鎖後,又講解了類鎖。Java中的synchronized
實現的對象鎖和類鎖的各種實現方式已經交代清楚,以及其實現原理也做了初步的解析。
但這些只是基礎部分,接下來的文章中,將重點分析7中不同情況下的synchronized
是否能夠保證線程安全,徹底理解synchronized
關鍵字的作用域,這樣才能不再使用同步的過程中,給自己挖坑,並且後續文章中,也是面試官常常問到場景,絕對不容錯過。喜歡本文,請點贊和收藏。