Java-類鎖和物件鎖
1.類鎖和物件鎖的定義
物件鎖的定義
是針對一個物件的,它只在該物件的某個記憶體位置宣告一個標誌位標識該物件是否擁有鎖,所以它只會鎖住當前的物件。一般一個物件鎖是對一個非靜態成員變數進行syncronized修飾,或者對一個非靜態方法進行syncronized修飾。對於物件鎖,不同物件訪問同一個被syncronized修飾的方法的時候不會阻塞住。
類鎖
類鎖是鎖住整個類的,當有多個執行緒來宣告這個類的物件的時候將會被阻塞,直到擁有這個類鎖的物件被銷燬或者主動釋放了類鎖。這個時候在被阻塞住的執行緒被挑選出一個佔有該類鎖,宣告該類的物件。其他執行緒繼續被阻塞住。
注意點
- 無論是類鎖還是物件鎖,父類和子類之間是否阻塞沒有直接關係。當對一個父類加了類鎖,子類是不會受到影響的,相反也是如此。因為synchronized關鍵字並不是方法簽名的一部分,它是對方法進行修飾的。當子類覆寫父類中的同步方法或是介面中宣告的同步方法的時候,synchronized修飾符是不會被自動繼承的,所以相應的阻塞問題不會出現。
- 構造方法不可能是真正同步的(儘管可以在構造方法中使用同步塊)。
2.如何宣告類鎖和物件鎖
類鎖的宣告方式
static void myMethod(){
synchronized(MyClass.class){
//code
}
}
等價於:
static synchronized void myMethod(){
// code
}
物件鎖的宣告方式
void myMethod(){
synchronized(this){
//code
}
}
等價於:
synchronized void myMethod(){
// code
}
3.程式碼測試
如下面程式碼,我聲明瞭一個類Runtest,在該類中包含無鎖方法noSyn、物件鎖方法 outMethod, outMethod2與一個類鎖方法plus。聲明瞭一個繼承了執行緒Thread的類Obj,在該類中用來訪問Runtest的方法,模擬各種測試場景。啟動測試類是MyDemo,在該類中有兩種測試方法,一種是宣告同一個測試類物件而開闢多個執行緒,用來測試物件鎖;另外一種是每當宣告一個新的執行緒則同時宣告一個新的測試類物件,用來測試類鎖。
具體測試流程分為兩個步驟。第一個步驟是直接執行如下程式碼,測試結果是用來測試物件鎖的鎖效果;第二個步驟是把for迴圈中的前兩行程式碼註釋掉,把其餘三行有註釋的程式碼刪去註釋,還有,類Obj最後一行註釋程式碼刪去註釋,用來測試類鎖的效果。
public class MyDemo{
public static void main(String[] args) {
Runtest runtest = new Runtest();
for (int i = 0; i < 10; i++) {
Thread thread = new Obj(runtest, i);// 1同一個RunTest1物件但每次有個新的執行緒
thread.start();
}
}
}
class Obj extends Thread {
Runtest r;
int i = 0;
public Obj(Runtest r, int i) {
this.r = r;
this.i = i;
}
public void run() {
r.noSyn(this.getId());
//用以測試同一個物件在不同執行緒中訪問不同方法
if(i % 2 == 0){
r.outMethod2();//物件鎖方法2
}else{
r.outMethod();//物件鎖方法1
}
//Runtest.plus(); //類鎖方法
}
}
class Runtest {
static int i = 0;
public void noSyn(long threadId) {
System.out.println("nosyn: class obj->" + this + ", threadId->" + threadId);
}
synchronized public void outMethod() {
System.out.println("in outMethod begin");
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
}
System.out.println("in outMethod end");
}
synchronized public void outMethod2() {
System.out.println("in outMethod2 begin");
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
}
System.out.println("in outMethod2 end");
}
public static void plus() {
synchronized (Runtest.class) {
System.out.println("start: " + i);
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
}
i++;
System.out.println("i is " + i);
}
}
}
執行結果:
按照上述測試步驟, 對於類鎖的測試結果如下:
第一部分輸出:
nosyn: class obj->test.Runtest@3851ddd2, threadId->10
in outMethod2 begin
nosyn: class obj->test.Runtest@1722fe15, threadId->13
in outMethod begin
nosyn: class obj->test.Runtest@6e1b0caf, threadId->15
nosyn: class obj->test.Runtest@31ddeda2, threadId->16
in outMethod2 begin
nosyn: class obj->test.Runtest@5be9d36, threadId->14
in outMethod2 begin
in outMethod begin
nosyn: class obj->test.Runtest@1624bef5, threadId->11
in outMethod begin
nosyn: class obj->test.Runtest@1f92ee25, threadId->18
in outMethod2 begin
nosyn: class obj->test.Runtest@2543472c, threadId->17
in outMethod begin
nosyn: class obj->test.Runtest@6750cf54, threadId->19
in outMethod begin
nosyn: class obj->test.Runtest@8afcd0c, threadId->12
in outMethod2 begin
/**************************上述輸出幾乎同時*********/
第二部分輸出:
in outMethod2 end
in outMethod end
in outMethod end
in outMethod2 end
in outMethod end
in outMethod end
in outMethod2 end
in outMethod end
in outMethod2 end
in outMethod2 end
/**************************sleep一段時間*********/
第三部分輸出:
start: 0
i is 1
start: 1
i is 2
start: 2
i is 3
start: 3
i is 4
start: 4
i is 5
start: 5
i is 6
start: 6
i is 7
start: 7
i is 8
start: 8
i is 9
start: 9
i is 10
對執行結果做出解釋:
-
它會首先執行沒加鎖的方法,無論是一個物件多個執行緒還是每個執行緒中一個物件,對無鎖方法都是沒有影響的。對於物件鎖和類鎖來說,只會對加了鎖的方法產生不同的影響。
-
當多個物件對同一個加了物件鎖的方法進行呼叫則會被阻塞,而不同物件對不同方法訪問則不會被阻塞,就算加了物件鎖,當同一個物件線上程1中訪問一個方法,線上程2中再去訪問另外一個加鎖方法,則同樣也會被阻塞。針對上面程式碼就是,線上程1中物件runTest訪問outMethod,而線上程2中訪問outMethod2則會被阻塞。
-
對於類鎖,則會把整個類鎖住,也就說只能有一個物件擁有當前類的鎖。當一個物件擁有了類鎖之後,另外一個物件還想競爭鎖的話則會被阻塞。兩個物件A,B,如果A正在訪問一個被類鎖修飾的方法function,那麼B則不能訪問。因為類鎖只能在同一時刻被一個物件擁有。相對於物件鎖,則是不同。還是A,B兩個物件,如果A正在訪問物件鎖修飾的function,那麼這個時候B也可以同時訪問。
對於類鎖的輸出進行分析,它的輸出我表示成三個部分:
在第一部分輸出幾乎同時輸出,是因為每個執行緒都是一個新的物件,不同物件訪問物件鎖是不會被阻塞的,所以幾乎是按照程式的先後輸出;
第二部分輸出就是兩個方法中的sleep時間消耗,沒有什麼問題;
第三部分就是計算i++,然後輸出結果,這部分輸出是比較慢的。因為plus方法是類鎖,在同一時刻只能是一個物件擁有該鎖,所以多個執行緒必須順序執行結果,所以最後i的輸出也是10.
其中對於物件鎖,當一個物件擁有鎖之後,訪問一個加了物件鎖的方法,而該方法中又呼叫了該類中其他加了物件鎖的方法,那麼這個時候是不會阻塞住的。這是java通過可重入鎖機制實現的。可重入鎖指的是當一個物件擁有物件鎖之後,可以重複獲取該鎖。因為synchronized塊是可重入的,所以當你訪問一個物件鎖的方法的時候,在該方法中繼續訪問其他物件鎖方法是不會被阻塞的。