Java並發包中Lock的實現原理,java並發包中lock

來源:互聯網
上載者:User

Java並發包中Lock的實現原理,java並發包中lock
Lock 的簡介及使用

         Lock是java 1.5中引入的線程同步工具,它主要用於多線程下共用資源的控制。本質上Lock僅僅是一個介面(位於源碼包中的java\util\concurrent\locks中),它包含以下方法

//嘗試擷取鎖,擷取成功則返回,否則阻塞當前線程 void lock(); //嘗試擷取鎖,線程在成功擷取鎖之前被中斷,則放棄擷取鎖,拋出異常 void lockInterruptibly() throws InterruptedException; //嘗試擷取鎖,擷取鎖成功則返回true,否則返回false boolean tryLock(); //嘗試擷取鎖,若在規定時間內擷取到鎖,則返回true,否則返回false,未擷取鎖之前被中斷,則拋出異常 boolean tryLock(long time, TimeUnit unit) throws InterruptedException; //釋放鎖 void unlock(); //返回當前鎖的條件變數,通過條件變數可以實作類別似notify和wait的功能,一個鎖可以有多個條件變數 Condition newCondition();

       Lock有三個實作類別,一個是ReentrantLock,另兩個是ReentrantReadWriteLock類中的兩個靜態內部類ReadLock和WriteLock。

          使用方法:多線程下訪問(互斥)共用資源時, 訪問前加鎖,訪問結束以後解鎖,解鎖的操作推薦放入finally塊中。

Lock l = ...; //根據不同的實現Lock介面類的建構函式得到一個鎖對象 l.lock(); //擷取鎖位於try塊的外面 try { // access the resource protected by this lock } finally
 {     l.unlock();}

         注意,加鎖位於對資源訪問的try塊的外部,特別是使用lockInterruptibly方法加鎖時就必須要這樣做,這為了防止線程在擷取鎖時被中斷,這時就不必(也不能)釋放鎖。

try {     l.lockInterruptibly();//擷取鎖失敗時不會執行finally塊中的unlock語句      try{          // access the resource protected by this lock     }finally{          l.unlock();     }} catch (InterruptedException e) {     // TODO Auto-generated catch block     e.printStackTrace();}
實現Lock介面的基本思想

          需要實現鎖的功能,兩個必備元素,一個是表示(鎖)狀態的變數(我們假設0表示沒有線程擷取鎖,1表示已有線程佔有鎖),另一個是隊列,隊列中的節點表示因未能擷取鎖而阻塞的線程。為瞭解決多核處理器下多線程緩衝不一致的問題,表示狀態的變數必須聲明為voaltile類型,並且對錶示狀態的變數和隊列的某些操作要保證原子性和可見度。原子性和可見度的操作主要通過Atomic包中的方法實現。

       線程擷取鎖的大致過程(這裡沒有考慮可重新進入和嘗試擷取鎖過程被中斷或逾時的情況)

          1. 讀取表示狀態的變數

        2. 如果表示狀態的變數的值為0,那麼當前線程嘗試將變數值設定為1(通過CAS操作完成),當多個線程同時將表示狀態的變數值由0設定成1時,僅一個線程能成功,其

           它線程都會失敗

            2.1 若成功,表示擷取了鎖,

                  2.1.1 如果該線程已位於在隊列中,則將其出列(並將下一個節點則變成了隊列的第一個節點)

                  2.1.2 如果該線程未入列,則不用對隊列進行維護

                  然後當前線程從lock方法中返回,對共用資源進行訪問。

             2.2 若失敗,則當前線程將自身放入等待(鎖的)隊列中並阻塞自身,此時線程一直被阻塞在lock方法中,沒有從該方法中返回(被喚醒後仍然在lock方法中,並回                    到第1步重新開始)

        3. 如果表示狀態的變數的值為1,那麼將當前線程放入等待隊列中,然後將自身阻塞(被喚醒後仍然在lock方法中,並回到第1步重新開始)

          注意,喚醒並不表示線程能立刻運行,而是表示線程處於就緒狀態,可以運行而已

 

      線程釋放鎖的大致過程

        1. 釋放鎖的線程將狀態變數的值從1設定為0,並喚醒等待(鎖)隊列中的隊首節點,釋放鎖的線程從就從unlock方法中返回,繼續執行線程後面的代碼

        2. 被喚醒的線程(隊列中的隊首節點)和可能和未進入隊列並且準備擷取的線程競爭擷取鎖,重複擷取鎖的過程

        注意:可能有多個線程同時競爭去擷取鎖,但是一次只能有一個線程去釋放鎖,隊列中的節點都需要它的前一個節點將其喚醒,例如有隊列A<-B-<C ,即由A釋放鎖時                        喚醒B,B釋放鎖時喚醒C

 

公平鎖和非公平鎖

         鎖可以分為公平鎖和不公平鎖,重入鎖和非重入鎖(關於重入鎖的介紹會在ReentrantLock原始碼分析中介紹),以上過程實際上是非公平鎖的擷取和釋放過程。

公平鎖嚴格按照先來後到的順去擷取鎖,而非公平鎖允許插隊擷取鎖。

          公平鎖擷取鎖的過程上有些不同,在使用公平鎖時,某線程想要擷取鎖,不僅需要判斷當前表示狀態的變數的值是否為0,還要判斷隊列裡是否還有其他線程,若隊列中還有線程則說明當前線程需要排隊,進行入列操作,並將自身阻塞;若隊列為空白,才能嘗試去擷取鎖。而對於非公平鎖,當表示狀態的變數的值是為0,就可以嘗試擷取鎖,不必理會隊列是否為空白,這樣就實現了插隊的特點。通常來說非公平鎖的吞吐率比公平鎖要高,我們一般常用非公平鎖。

           這裡需要解釋一點,什麼情況下才會出現,表示鎖的狀態的變數的值是為0而且隊列中仍有其它線程等待擷取鎖的情況。

           假設有三個線程A、B、C。A線程為正在啟動並執行線程並持有鎖,隊列中有一個C線程,位於隊首。現在A線程要釋放鎖,具體執行的過程操作可分為兩步:

            1. 將表示鎖狀態的變數值由1變為0,

            2. C線程被喚醒,這裡要明確兩點:(1)C線程被喚醒並不代表C線程開始執行,C線程此時是處於就緒狀態,要等待CPU的輪詢(2)C線程目前還並未出列,C線程                   要進入運行狀態,並且通過競爭擷取到鎖以後才會出列。

            如果C線程此時還沒有進入運行態,同時未在隊列中的B線程進行擷取鎖的操作,B就會發現雖然當前沒有線程持有鎖,但是隊列不為空白(C線程仍然位於隊列中),要滿足先來後到的特點(B在C之後執行擷取鎖的操作),B線程就不能去嘗試擷取鎖,而是進行入列操作。

 

實現Condition介面的基本思想

         Condition 本質是一個介面,它包含如下方法

// 讓線程進入等待通知狀態,在未接受到通知之前,可通過中斷結束等待狀態,並拋出異常

void await() throws InterruptedException;

// 讓線程進入等通知待狀態,無法被中斷

void awaitUninterruptibly();

//讓線程進入等待通知狀態,逾時結束等待狀態,並拋出異常

long awaitNanos(long nanosTimeout) throws InterruptedException;

boolean await(long time, TimeUnit unit) throws InterruptedException;

boolean awaitUntil(Date deadline) throws InterruptedException;

//將同一等待條件下的一個線程,從等待通知狀態轉換為等待鎖狀態

void signal();

//將同一等待條件下的所有個線程,從等待通知阻塞狀態轉換為等待鎖阻塞狀態

 void signalAll();

           一個Condition執行個體的內部實際上維護了一個隊列,隊列中的節點表示由於(某些條件不滿足而)線程自身調用await方法阻塞的線程。Condition介面中有兩個重要的方法,即 await方法和 signal方法。線程調用這個方法之前該線程必須已經擷取了Condition執行個體所依附的鎖。這樣的原因有兩個,1對於await方法,它內部會執行釋放鎖的操作,所以使用前必須擷取鎖2對於signal方法,是為了避免多個線程同時調用同一個Condition執行個體的singal方法時引起的(隊列)出列競爭。下面是這兩個方法的執行流程。

          await方法:

                            1. 入列到條件隊列(這裡不是等待鎖的隊列

                            2. 釋放鎖

                             3. 阻塞自身線程

                             ------------被喚醒後執行-------------

                            4. 嘗試去擷取鎖(執行到這裡時線程已不在條件隊列中,而是位於等待(鎖的)隊列中,參見signal方法)

                                4.1 成功,從await方法中返回,執行線程後面的代碼

                                4.2 失敗,阻塞自己(等待前一個節點釋放鎖時將它喚醒)

         注意:await方法時自身線程調用的,線程在await方法中阻塞,並沒有從await方法中返回,當喚醒後繼續執行await方法中後面的代碼。可以看出await方法釋放了鎖,又嘗                     試獲得鎖。

 

         signal方法:

                           1. 將條件隊列的隊首節點取出,放入等待鎖隊列的隊尾

                           2. 喚醒該節點對應的線程

         注意:signal是由其它線程調用

           下面這個例子,就是利用lock和condition實現B線程先列印一句資訊後,然後A線程列印兩句資訊(不能中斷),交替十次後結束

public class ConditionDemo {    volatile int key = 0;    Lock l = new ReentrantLock();    Condition c = l.newCondition();        public static  void main(String[] args){        ConditionDemo demo = new ConditionDemo();        new Thread(demo.new A()).start();        new Thread(demo.new B()).start();    }        class A implements Runnable{        @Override        public void run() {            int i = 10;            while(i > 0){                l.lock();                try{                    if(key == 1){                        System.out.println("A is Running");                        System.out.println("A is Running");                        i--;                        key = 0;                        c.signal();                    }else{                        c.awaitUninterruptibly();                                            }                                    }                finally{                    l.unlock();                }            }        }            }        class B implements Runnable{        @Override        public void run() {            int i = 10;            while(i > 0){                l.lock();                try{                    if(key == 0){                        System.out.println("B is Running");                        i--;                        key = 1;                        c.signal();                    }else{                        c.awaitUninterruptibly();                                            }                                    }                finally{                    l.unlock();                }            }        }        }}
Lock與synchronized的區別

1. Lock的加鎖和解鎖都是由java代碼配合native方法(叫用作業系統的相關方法)實現的,而synchronize的加鎖和解鎖的過程是由JVM管理的

2. 當一個線程使用synchronize擷取鎖時,若鎖被其他線程佔用著,那麼當前只能被阻塞,直到成功擷取鎖。而Lock則提供逾時鎖和可中斷等更加靈活的方式,在未能擷取鎖的     條件下提供一種退出的機制。

3. 一個鎖內部可以有多個Condition執行個體,即有多路條件隊列,而synchronize只有一路條件隊列;同樣Condition也提供靈活的阻塞方式,在未獲得通知之前可以通過中斷線程以    及設定等待時限等方式允出準則隊列。

4. synchronize對線程的同步僅提供獨佔模式,而Lock即可以提供獨佔模式,也可以提供共用模式

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.