1. 程式人生 > >Java物件鎖和類鎖全面解析(多執行緒synchronized關鍵字)

Java物件鎖和類鎖全面解析(多執行緒synchronized關鍵字)

最近工作有用到一些多執行緒的東西,之前吧,有用到synchronized同步塊,不過是別人怎麼用就跟著用,並沒有搞清楚鎖的概念。最近也是遇到一些問題,不搞清楚鎖的概念,很容易碰壁,甚至有些時候自己連用沒用對都不知道。

今天把一些疑惑都解開了,寫篇文章分享給大家,文章還算比較全面。當然可能有小寶鴿理解得不夠深入透徹的地方,如果說得不正確還望指出。

看之前有必要跟某些猿友說一下,如果看一遍沒有看明白呢,也沒關係,當是瞭解一下,等真正使用到了,再回頭看。

本文主要是將synchronized關鍵字用法作為例子來去解釋Java中的物件鎖和類鎖。特別的是希望能幫大家理清一些概念。

一、synchronized關鍵字

synchronized關鍵字有如下兩種用法:

1、 在需要同步的方法的方法簽名中加入synchronized關鍵字。

synchronized public void getValue() {
    System.out.println("getValue method thread name="
            + Thread.currentThread().getName() + " username=" + username
            + " password=" + password);
}

上面的程式碼修飾的synchronized是非靜態方法,如果修飾的是靜態方法(static)含義是完全不一樣的。具體不一樣在哪裡,後面會詳細說清楚。

synchronized static public void getValue() {
    System.out.println("getValue method thread name="
            + Thread.currentThread().getName() + " username=" + username
            + " password=" + password);
}

2、使用synchronized塊對需要進行同步的程式碼段進行同步。

public void serviceMethod() {
    try {
        synchronized (this
) { System.out.println("begin time=" + System.currentTimeMillis()); Thread.sleep(2000); System.out.println("end end=" + System.currentTimeMillis()); } } catch (InterruptedException e) { e.printStackTrace(); } }

上面的程式碼塊是synchronized (this)用法,還有synchronized (非this物件)以及synchronized (類.class)這兩種用法,這些使用方式的含義也是有根本的區別的。我們先帶著這些問題繼續往下看。

二、Java中的物件鎖和類鎖

小寶鴿似乎並沒有辦法用清晰簡短的語言來描述物件鎖和類鎖的概念。即便能用簡單的語句概況,也會顯得抽象。猿友們耐心看完自然會明白。

之前網上有找一些相關資料,有篇部落格是這樣描述的(看的是轉載的,原創連線我也不知道):

一段synchronized的程式碼被一個執行緒執行之前,他要先拿到執行這段程式碼的許可權,
在Java裡邊就是拿到某個同步物件的鎖(一個物件只有一把鎖);
如果這個時候同步物件的鎖被其他執行緒拿走了,他(這個執行緒)就只能等了(執行緒阻塞在鎖池等待佇列中)。 
取到鎖後,他就開始執行同步程式碼(被synchronized修飾的程式碼);
執行緒執行完同步程式碼後馬上就把鎖還給同步物件,其他在鎖池中等待的某個執行緒就可以拿到鎖執行同步程式碼了。
這樣就保證了同步程式碼在統一時刻只有一個執行緒在執行。

上面提到鎖,這裡先引出鎖的概念。先來看看下面這些囉嗦而必不可少的文字。

多執行緒的執行緒同步機制實際上是靠鎖的概念來控制的。

在Java程式執行時環境中,JVM需要對兩類執行緒共享的資料進行協調:
1)儲存在堆中的例項變數
2)儲存在方法區中的類變數

這兩類資料是被所有執行緒共享的。
(程式不需要協調儲存在Java 棧當中的資料。因為這些資料是屬於擁有該棧的執行緒所私有的。)

這裡插播一下廣告:關於JVM記憶體,如果想了解可以看看博主的另外一篇文章:

方法區(Method Area)與Java堆一樣,是各個執行緒共享的記憶體區域,它用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。雖然Java虛擬機器規範把方法區描述為堆的一個邏輯部分,但是它卻有一個別名叫做Non-Heap(非堆),目的應該是與Java堆區分開來。

:在Java中,JVM中的棧記錄了執行緒的方法呼叫。每個執行緒擁有一個棧。在某個執行緒的執行過程中,如果有新的方法呼叫,那麼該執行緒對應的棧就會增加一個儲存單元,即幀(frame)。在frame中,儲存有該方法呼叫的引數、區域性變數和返回地址。

是JVM中一塊可自由分配給物件的區域。當我們談論垃圾回收(garbage collection)時,我們主要回收堆(heap)的空間。
Java的普通物件存活在堆中。與棧不同,堆的空間不會隨著方法呼叫結束而清空。因此,在某個方法中建立的物件,可以在方法呼叫結束之後,繼續存在於堆中。這帶來的一個問題是,如果我們不斷的建立新的物件,記憶體空間將最終消耗殆盡。

在java虛擬機器中,每個物件和類在邏輯上都是和一個監視器相關聯的。
對於物件來說,相關聯的監視器保護物件的例項變數。

對於類來說,監視器保護類的類變數。

(如果一個物件沒有例項變數,或者一個類沒有變數,相關聯的監視器就什麼也不監視。)
為了實現監視器的排他性監視能力,java虛擬機器為每一個物件和類都關聯一個鎖。代表任何時候只允許一個執行緒擁有的特權。執行緒訪問例項變數或者類變數不需鎖。

但是如果執行緒獲取了鎖,那麼在它釋放這個鎖之前,就沒有其他執行緒可以獲取同樣資料的鎖了。(鎖住一個物件就是獲取物件相關聯的監視器)

類鎖實際上用物件鎖來實現。當虛擬機器裝載一個class檔案的時候,它就會建立一個java.lang.Class類的例項。當鎖住一個物件的時候,實際上鎖住的是那個類的Class物件。

一個執行緒可以多次對同一個物件上鎖。對於每一個物件,java虛擬機器維護一個加鎖計數器,執行緒每獲得一次該物件,計數器就加1,每釋放一次,計數器就減 1,當計數器值為0時,鎖就被完全釋放了。

java程式設計人員不需要自己動手加鎖,物件鎖是java虛擬機器內部使用的。

在java程式中,只需要使用synchronized塊或者synchronized方法就可以標誌一個監視區域。當每次進入一個監視區域時,java 虛擬機器都會自動鎖上物件或者類。

三、synchronized關鍵字各種用法與例項

看完了”二、Java中的物件鎖和類鎖”,我們再來結合”一、synchronized關鍵字”裡面提到的synchronized用法。

事實上,synchronized修飾非靜態方法、同步程式碼塊的synchronized (this)用法和synchronized (非this物件)的用法鎖的是物件,執行緒想要執行對應同步程式碼,需要獲得物件鎖。

synchronized修飾靜態方法以及同步程式碼塊的synchronized (類.class)用法鎖的是類,執行緒想要執行對應同步程式碼,需要獲得類鎖。

因此,事實上synchronized關鍵字可以細分為上面描述的五種用法。

本文的例項均來自於《Java多執行緒程式設計核心技術》這本書裡面的例子。

1、我們先看看非執行緒安全例項(Run.java):

public class Run {

    public static void main(String[] args) {

        HasSelfPrivateNum numRef = new HasSelfPrivateNum();

        ThreadA athread = new ThreadA(numRef);
        athread.start();

        ThreadB bthread = new ThreadB(numRef);
        bthread.start();

    }

}

class HasSelfPrivateNum {

    private int num = 0;

    public void addI(String username) {
        try {
            if (username.equals("a")) {
                num = 100;
                System.out.println("a set over!");
                Thread.sleep(2000);
            } else {
                num = 200;
                System.out.println("b set over!");
            }
            System.out.println(username + " num=" + num);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}


class ThreadA extends Thread {

    private HasSelfPrivateNum numRef;

    public ThreadA(HasSelfPrivateNum numRef) {
        super();
        this.numRef = numRef;
    }

    @Override
    public void run() {
        super.run();
        numRef.addI("a");
    }

}



 class ThreadB extends Thread {

    private HasSelfPrivateNum numRef;

    public ThreadB(HasSelfPrivateNum numRef) {
        super();
        this.numRef = numRef;
    }

    @Override
    public void run() {
        super.run();
        numRef.addI("b");
    }

}

執行結果為:

a set over!
b set over!
b num=200
a num=200

修改HasSelfPrivateNum如下,方法用synchronized修飾如下:

class HasSelfPrivateNum {

    private int num = 0;

    synchronized public void addI(String username) {
        try {
            if (username.equals("a")) {
                num = 100;
                System.out.println("a set over!");
                Thread.sleep(2000);
            } else {
                num = 200;
                System.out.println("b set over!");
            }
            System.out.println(username + " num=" + num);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

執行結果是執行緒安全的:

b set over!
b num=200
a set over!
a num=100

實驗結論:兩個執行緒訪問同一個物件中的同步方法是一定是執行緒安全的。本實現由於是同步訪問,所以先打印出a,然後打印出b

這裡執行緒獲取的是HasSelfPrivateNum的物件例項的鎖——物件鎖。

2、多個物件多個鎖

就上面的例項,我們將Run改成如下:

public class Run {

    public static void main(String[] args) {

        HasSelfPrivateNum numRef1 = new HasSelfPrivateNum();
        HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();

        ThreadA athread = new ThreadA(numRef1);
        athread.start();

        ThreadB bthread = new ThreadB(numRef2);
        bthread.start();

    }

}

執行結果為:

a set over!
b set over!
b num=200
a num=200

這裡是非同步的,因為執行緒athread獲得是numRef1的物件鎖,而bthread執行緒獲取的是numRef2的物件鎖,他們並沒有在獲取鎖上有競爭關係,因此,出現非同步的結果

這裡插播一下:同步不具有繼承性

3、同步塊synchronized (this)

我們先看看程式碼例項(Run.java)

public class Run {

    public static void main(String[] args) {
        ObjectService service = new ObjectService();

        ThreadA a = new ThreadA(service);
        a.setName("a");
        a.start();

        ThreadB b = new ThreadB(service);
        b.setName("b");
        b.start();
    }

}

class ObjectService {

    public void serviceMethod() {
        try {
            synchronized (this) {
                System.out.println("begin time=" + System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println("end    end=" + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


class ThreadA extends Thread {

    private ObjectService service;

    public ThreadA(ObjectService service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.serviceMethod();
    }

}


class ThreadB extends Thread {
    private ObjectService service;

    public ThreadB(ObjectService service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.serviceMethod();
    }
}

執行結果:

begin time=1466148260341
end    end=1466148262342
begin time=1466148262342
end    end=1466148264378

這樣也是同步的,執行緒獲取的是同步塊synchronized (this)括號()裡面的物件例項的物件鎖,這裡就是ObjectService例項物件的物件鎖了。

需要注意的是synchronized (){}的{}前後的程式碼依舊是非同步的

4、synchronized (非this物件)

我們先看看程式碼例項(Run.java)

public class Run {

    public static void main(String[] args) {

        Service service = new Service("xiaobaoge");

        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();

    }

}

class Service {

    String anyString = new String();

    public Service(String anyString){
        this.anyString = anyString;
    }

    public void setUsernamePassword(String username, String password) {
        try {
            synchronized (anyString) {
                System.out.println("執行緒名稱為:" + Thread.currentThread().getName()
                        + "在" + System.currentTimeMillis() + "進入同步塊");
                Thread.sleep(3000);
                System.out.println("執行緒名稱為:" + Thread.currentThread().getName()
                        + "在" + System.currentTimeMillis() + "離開同步塊");
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

class ThreadA extends Thread {
    private Service service;

    public ThreadA(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.setUsernamePassword("a", "aa");

    }

}


class ThreadB extends Thread {

    private Service service;

    public ThreadB(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.setUsernamePassword("b", "bb");

    }

}

不難看出,這裡執行緒爭奪的是anyString的物件鎖,兩個執行緒有競爭同一物件鎖的關係,出現同步

現在有一個問題:一個類裡面有兩個非靜態同步方法,會有影響麼?

答案是:如果物件例項A,執行緒1獲得了物件A的物件鎖,那麼其他執行緒就不能進入需要獲得物件例項A的物件鎖才能訪問的同步程式碼(包括同步方法和同步塊)。不理解可以細細品味一下!

5、靜態synchronized同步方法

我們直接看程式碼例項:

public class Run {

    public static void main(String[] args) {

        ThreadA a = new ThreadA();
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB();
        b.setName("B");
        b.start();

    }

}

class Service {

    synchronized public static void printA() {
        try {
            System.out.println("執行緒名稱為:" + Thread.currentThread().getName()
                    + "在" + System.currentTimeMillis() + "進入printA");
            Thread.sleep(3000);
            System.out.println("執行緒名稱為:" + Thread.currentThread().getName()
                    + "在" + System.currentTimeMillis() + "離開printA");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public static void printB() {
        System.out.println("執行緒名稱為:" + Thread.currentThread().getName() + "在"
                + System.currentTimeMillis() + "進入printB");
        System.out.println("執行緒名稱為:" + Thread.currentThread().getName() + "在"
                + System.currentTimeMillis() + "離開printB");
    }

}


class ThreadA extends Thread {
    @Override
    public void run() {
        Service.printA();
    }

}


class ThreadB extends Thread {
    @Override
    public void run() {
        Service.printB();
    }
}

執行結果:

執行緒名稱為:A1466149372909進入printA
執行緒名稱為:A1466149375920離開printA
執行緒名稱為:B在1466149375920進入printB
執行緒名稱為:B在1466149375920離開printB

兩個執行緒在爭奪同一個類鎖,因此同步

6、synchronized (class)

對上面Service類程式碼修改成如下:

class Service {

    public static void printA() {
        synchronized (Service.class) {
            try {
                System.out.println("執行緒名稱為:" + Thread.currentThread().getName()
                        + "在" + System.currentTimeMillis() + "進入printA");
                Thread.sleep(3000);
                System.out.println("執行緒名稱為:" + Thread.currentThread().getName()
                        + "在" + System.currentTimeMillis() + "離開printA");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    public static void printB() {
        synchronized (Service.class) {
            System.out.println("執行緒名稱為:" + Thread.currentThread().getName()
                    + "在" + System.currentTimeMillis() + "進入printB");
            System.out.println("執行緒名稱為:" + Thread.currentThread().getName()
                    + "在" + System.currentTimeMillis() + "離開printB");
        }
    }
}

執行結果:

執行緒名稱為:A1466149372909進入printA
執行緒名稱為:A1466149375920離開printA
執行緒名稱為:B在1466149375920進入printB
執行緒名稱為:B在1466149375920離開printB

兩個執行緒依舊在爭奪同一個類鎖,因此同步

需要特別說明:對於同一個類A,執行緒1爭奪A物件例項的物件鎖,執行緒2爭奪類A的類鎖,這兩者不存在競爭關係。也就說物件鎖和類鎖互補干預內政

靜態方法則一定會同步,非靜態方法需在單例模式才生效,但是也不能都用靜態同步方法,總之用得不好可能會給效能帶來極大的影響。另外,有必要說一下的是Spring的bean預設是單例的。

相關推薦

Java物件全面解析執行synchronized關鍵字

最近工作有用到一些多執行緒的東西,之前吧,有用到synchronized同步塊,不過是別人怎麼用就跟著用,並沒有搞清楚鎖的概念。最近也是遇到一些問題,不搞清楚鎖的概念,很容易碰壁,甚至有些時候自己連用沒用對都不知道。 今天把一些疑惑都解開了,寫篇文章分享給大家

Java執行synchronized關鍵字引出的多種

一、synchronized機制 synchronized關鍵字是JAVA中常用的同步功能,提供了簡單易用的鎖功能。 synchronized有三種用法,分別為: 用在普通方法上,能夠鎖住當前物件。 用在靜態方法上,能夠鎖住類 用在程式碼塊上,鎖住的是synchronized()裡的物件 在JDK6之前

Java之UDP傳輸聊天程式小Demo執行

ChatDemo.java import java.net.DatagramSocket; public class ChatDemo { public static void main(String[] args) { try {

javaSE (三十九網路程式設計網路程式設計三要素Socket、UDP傳輸、執行UDP傳輸

1、網路程式設計三要素: 地址:定位電腦 本地迴路地址:127.0.0.1 廣播地址:255.255.255.255 埠號:定位電腦中的程式 o~65525 儘量使用1024以上的 協議:資料交換的規則/標準 UDP: 面向無連線,資料不安全,速度快,不區分客戶端與服

Java併發程式設計3 —— 物件

synchronized關鍵字作用在同步程式碼塊上,給共享變數“上鎖”可以解決執行緒安全問題。這把“鎖”可以作用在某個物件上,也可以作用在某個類上。 舉個栗子,有個自助銀行,裡面有兩臺ATM機,工作人員可以看到每次存取款之後機器裡鈔票的總金額數。現在有兩個人來存錢,各存50

Java物件

Java中的鎖:(簡要描述) 多執行緒的執行緒同步機制實際上是靠鎖的概念來控制的。 在Java程式執行時環境中,JVM需要對兩類執行緒共享的資料進行協調: 1)儲存在堆中的例項變數 2)儲存在方法區中的類變數 這兩類資料是被所有執行緒共享的。 (程式不需要協調儲存在Jav

Java的sychronized物件的區別

  4. 同步加鎖的是物件,而不是程式碼。因此,如果你的類中有一個同步方法,這個方法可以被兩個不同的執行緒同時執行,只要每個執行緒自己建立一個的該類的例項即可。  5. 不同的物件例項的synchronized方法是不相干擾的。也就是說,其它執行緒照樣可以同時訪問相同類的另一個物件例項中的synchroniz

透徹理解 Java synchronized 物件的區別

synchronized 加到 static 方法前面是給class 加鎖,即類鎖;而synchronized 加到非靜態方法前面是給物件上鎖。這兩者的區別我用程式碼來演示下: 物件鎖和類鎖是不同的鎖,所以多個執行緒同時執行這2個不同鎖的方法時,是非同步的。

方法物件區別

synchronized用來處理多個執行緒同時訪問同一個類的一個程式碼塊、方法,甚至這個類。 (1)修飾程式碼塊時,需要設定一個參考物件作為鎖的物件(物件鎖)。 (2)修飾方法時,預設是當前對線作為鎖的物件。 (3)修飾類時,預設是當前類的Class物件作為鎖的物件。 1、物件鎖

--自旋、阻塞、可重入、悲觀、樂觀、讀寫、偏向所、輕量級、重量級膨脹、物件

參考:http://blog.csdn.net/a314773862/article/details/54095819 自旋鎖 自旋鎖可以使執行緒在沒有取得鎖的時候,不被掛起,而轉去執行一個空迴圈,(即所謂的自旋,就是自己執行空迴圈),若在若干個空迴圈後,執行緒如果可以獲得

面試官:請說一下物件的區別

有鎖才有自由 生活中不存在絕對的自由,絕對的自由通常對應的無序和混沌,只有在道德、法律、倫理的約束下的相對自由,才能使人感受到自由。 而在多執行緒程式設計中,鎖是至關重要的,鎖就是道德,就是法律約束,沒有鎖的多執行緒環境將會是混亂的,所有執行緒都在爭奪資源,最後的結果就是導致系統崩潰,而有了鎖之後,多執行緒環

java架構之路執行AQS之ReetrantLock顯示的使用底層原始碼解讀

  說完了我們的synchronized,這次我們來說說我們的顯示鎖ReetrantLock。 上期回顧:   上次部落格我們主要說了鎖的分類,synchronized的使用,和synchronized隱式鎖的膨脹升級過程,從無鎖是如何一步步升級到我們的重量級鎖的,還有我們的逃逸分析。 鎖的粗化和鎖的消除  

執行synchronized XXX.classthis的區別記錄

Mythread extends Thread synchronized (Mythread.class) {     //鎖類  即此類的例項擁有共同的鎖 即鎖共有 @@可以當成是類變數的感覺..所以物件共享 } synchronized (th

java 執行synchronized同步方法,同步程式碼塊

執行緒安全問題 同步和非同步 我們知道多個執行緒共享堆記憶體,當兩個或者多個執行緒呼叫同一個物件的方法操作物件成員時,因為cpu輪流執行執行緒,執行緒A剛開始操作物件方法,修改了資料,輪到執行緒B執行,執行緒B也操作物件方法,修改資料,可能又輪到執行緒A操作物件方法,接著上次執行緒A的剩餘部

入坑JAVA執行併發(六

  在多執行緒的中,因為要保證執行緒安全,需要對一些操作進行加鎖,但是如果操作不當,會造成死鎖,導致程式無法執行下去。   形成死鎖的場景:如果有兩個執行緒,執行緒1和執行緒2,執行緒1執行,獲得鎖A,執行緒2執行,獲得B,執行緒1等待鎖B的釋放,執行緒2等待

java--執行建立的兩種方式ThreadRunnable介面

(一)繼承Thread類建立多執行緒----單執行緒下面的程式碼是一個死迴圈,但是不會執行main裡面的迴圈語句,而是run()裡面的語句,這是因為該程式是一個單執行緒程式,當呼叫MyThread類的run()方法時,遇到死迴圈,迴圈一直進行。因此,MyThread類的列印

java執行synchronized的使用及死的造成和解

1.synchronized在不同位置鎖的內容 實際就是鎖物件和鎖類(不準確的說法)的區別。通常的用法都好理解,需要注意的是synchronized Method。如果方法是static的,那麼很好理解,實際上也是鎖了class;如果是普通方法則向當於物件鎖s

執行synchronized XXX.classthis的區別記錄

Mythread extends Threadsynchronized (Mythread.class) {    //鎖類  即此類的例項擁有共同的鎖 即鎖共有 @@可以當成是類變數的感覺..所以物件共享}synchronized (this) {    //鎖物件  即各

java 執行 synchronized重入

public class Service { synchronized public void service1(){ System.out.println("service1"); service2(); } syn

java執行同步以及執行間通訊詳解&消費者生產者模式&死&Thread.join()執行程式設計之二

從執行結果,我們就可以看出我們4個售票視窗同時賣出了1號票,這顯然是不合邏輯的,其實這個問題就是我們前面所說的執行緒同步問題。不同的執行緒都對同一個資料進了操作這就容易導致資料錯亂的問題,也就是執行緒不同步。那麼這個問題該怎麼解決呢?在給出解決思路之前我們先來分析一下這個問題是怎麼產生的?我們宣告一個執行緒類