1. 程式人生 > >Java鎖機制

Java鎖機制

使用 分享 在那 class類 成員 數據 控制臺 虛擬 too

線程同步

什麽是線程同步

線程之間執行是有先後順序的,一個線程要等待上一個線程執行完之後才開始執行當前的線程。

為什麽要線程同步

java允許多線程並發控制,當多個線程同時操作一個可共享的資源變量時(如數據的增刪改查), 將會導致數據不準確,相互之間產生沖突,所以需要線程同步執行,保證了該變量的唯一性和準確性。

如何實現線程同步

多線程的線程同步機制實際上是靠鎖的概念來控制的。 1)synchronized關鍵字 2)java.util.concurrent.lock包中的Lock對象

synchronized關鍵字
synchronized 是Java的關鍵字,是Java的內置特性,在JVM層面實現了對臨界資源的同步互斥訪問。

1. 在Java程序運行時環境中,JVM需要對兩類線程共享的數據進行協調:1)保存在堆中的實例變量  2)保存在方法區中的類變量  這兩類數據是被所有線程共享的(Java棧總的數據是線程私有的,不需要協調)。

2. 在java虛擬機中,每個對象和類在邏輯上都是和一個監視器相關聯的。對於對象來說,相關聯的監視器保護對象的實例變量。對於類來說,監視器保護類的類變量。(如果一個對象沒有實例變量,或者一個類沒有變量,相關聯的監視器就什麽也不監視。)

3. 為了實現監視器的排他性監視能力,java虛擬機為每一個對象和類都關聯一個鎖。代表任何時候只允許一個線程擁有的特權。 如果線程獲取了鎖,那麽在它釋放這個鎖之前,就沒有其他線程可以獲取同樣數據的鎖了。(鎖住一個對象就是獲取對象相關聯的監視器)

類鎖實際上用對象鎖來實現。當虛擬機裝載一個class文件的時候,它就會創建一個java.lang.Class類的實例。當鎖住一個對象的時候,實際上鎖住的是那個類的Class對象。

4. 一個線程可以多次對同一個對象上鎖。對於每一個對象,java虛擬機維護一個加鎖計數器,線程每獲得一次該對象,計數器就加1,每釋放一次,計數器就減 1,當計數器值為0時,鎖就被完全釋放了。java編程人員不需要自己動手加鎖,對象鎖是java虛擬機內部使用的。

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

使用方式

1. synchronized關鍵字修飾方法。public synchronized void save(){}  synchronized關鍵字也可以修飾靜態方法,此時如果調用該靜態方法,將會鎖住整個類

2. synchronized關鍵字修飾代碼塊。synchronized(object){}  同步是一種高開銷的操作,因此應該盡量減少同步的內容。 通常沒有必要同步整個方法,使用synchronized代碼塊同步關鍵代碼即可。

案例1

技術分享圖片
public class ThreadTest extends Thread {
    private int threadNo;

    public ThreadTest(int threadNo) {
        this.threadNo = threadNo;
    }

    @Override
    public synchronized void run() {
        for(int i = 1; i < 10000; i++){
            System.out.println("No." + threadNo + ":" + i);
        }
    }

    public static void main(String[] args) throws Exception {
        for(int i = 1; i < 10; i++){
            new ThreadTest(i).start();
            Thread.sleep(1);
        }
    }

}
技術分享圖片

這個程序其實就是讓10個線程在控制臺上數數,從1數到9999。理想情況下,我們希望看到一個線程數完,然後才是另一個線程開始數數。但是這個程序的執行過程告訴我們,這些線程還是亂糟糟的在那裏搶著報數,絲毫沒有任何規矩可言。

但是細心的讀者註意到:run方法還是加了一個synchronized關鍵字的,按道理說,這些線程應該可以一個接一個的執行這個run方法才對阿。

對於一個成員方法加synchronized關鍵字,這實際上是以這個成員方法所在的對象本身作為對象鎖

在本例中就是 以ThreadTest類的一個具體對象,也就是該線程自身作為對象鎖的。一共十個線程,每個線程持有自己 線程對象的那個對象鎖。這必然不能產生同步的效果。

案例2

技術分享圖片
public class ThreadTest extends Thread {
    private int threadNo;
    private String lock;

    public ThreadTest(int threadNo, String lock) {
        this.threadNo = threadNo;
        this.lock = lock;
    }

    public void run() {
        synchronized(lock){
            for(int i = 1; i < 10000; i++){
                System.out.println("No." + threadNo + ":" + i);
            }
        }
    }

    public static void main(String[] args) throws Exception {
        String lock = new String("lock");
        for(int i = 1; i < 10; i++){
            new ThreadTest(i, lock).start();
            Thread.sleep(1);
        }
    }

}
技術分享圖片

該程序通過在main方法啟動10個線程之前,創建了一個String類型的對象。並通過ThreadTest的構造函數,將這個對象賦值給每一個ThreadTest線程對象中的私有變量lock。

根據Java方法的傳值特點,這些線程的lock變量實際上指向的是堆內存中的同一個區域,即存放main函數中的lock變量的區域。

程序將原來run方法前的synchronized關鍵字去掉,換用了run方法中的一個synchronized塊來實現。這個同步塊的對象鎖,就是 main方法中創建的那個String對象。換句話說,他們指向的是同一個String類型的對象,對象鎖是共享且唯一的!

於是,我們看到了預期的效果:10個線程不再是爭先恐後的報數了,而是一個接一個的報數。

案例3

技術分享圖片
public class ThreadTest extends Thread {
    private int threadNo;

    public ThreadTest(int threadNo) {
        this.threadNo = threadNo;
    }

    public void run() {
        abc(threadNo);
    }

    public static void main(String[] args) throws Exception {
        for(int i = 1; i < 20; i++){
            new ThreadTest(i).start();
            Thread.sleep(1);
        }
    }

    public static synchronized void abc(int threadNo) {
        for(int i = 1; i < 10000; i++){
            System.out.println("No." + threadNo + ":" + i);
        }
    }

}
技術分享圖片

這段代碼沒有使用main方法中創建的String對象作為這10個線程的線程鎖。而是通過在run方法中調用本線程中一個靜態的同步 方法abc而實現了線程的同步。

這裏synchronized靜態方法是用什麽來做對象鎖的呢?對於同步靜態方法,對象鎖就是該靜態放發所在的類的Class實例,由於在JVM中,所有被加載的類都有唯一的類對象,具體到本例就是唯一的 ThreadTest.class對象。不管我們創建了該類的多少實例,但是它的類實例仍然是一個!


總結

1. 對於同步的方法或者代碼塊來說,必須獲得對象鎖才能夠進入同步方法或者代碼塊進行操作;

2. 如果采用method級別的同步,則對象鎖即為method所在的對象,如果是靜態方法,對象鎖即指method所在的Class對象(唯一);

3. 對於代碼塊,對象鎖即指synchronized(abc)中的abc;

4. 同步有兩種方式,同步塊和同步方法。

如果是同步代碼塊,則對象鎖需要編程人員自己指定,一般有些代碼為synchronized(this)只有在單態模式才生效。如果是同步方法,則分靜態和非靜態兩種,靜態方法則一定會同步,非靜態方法需在單例模式才生效,推薦用靜態方法(不用擔心是否單例)。

5. 在Java多線程編程中,最常見的synchronized關鍵字實際上是依靠對象鎖的機制來實現線程同步的。

Lock詳解http://www.cnblogs.com/aishangJava/p/6555291.html

死鎖問題

所謂死鎖:是指兩個或兩個以上的進程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態 或系統產生了死鎖,這些永遠在互相等待的進程稱為死鎖進程。

產生死鎖的條件

互斥條件

指進程對所分配到的資源進行排它性使用,即在一段時間內某資源只由一個進程占用。如果此時還有其它進程請求資源,則請求者只能等待,直至占有資源的進程用畢釋放。

請求和保持條件

指進程已經保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程占有,此時請求進程阻塞,但又對自己已獲得的其它資源保持不放。

不剝奪條件

進程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時由自己釋放。

環路等待條件

指在發生死鎖時,必然存在一個進程——資源的環形鏈,即進程集合{P0,P1,P2,···,Pn}中的P0正在等待一個P1占用的資源;P1正在等待P2占用的資源,……,Pn正在等待已被P0占用的資源。

處理方法

預防死鎖:破壞四個必要條件中的一個或多個

避免死鎖

檢測死鎖

解除死鎖

Java鎖機制