1. 程式人生 > >多執行緒(五) java的執行緒鎖

多執行緒(五) java的執行緒鎖

 在多執行緒中,每個執行緒的執行順序,是無法預測不可控制的,那麼在對資料進行讀寫的時候便存在由於讀寫順序多亂而造成資料混亂錯誤的可能性。那麼如何控制,每個執行緒對於資料的讀寫順序呢?這裡就涉及到執行緒鎖。

什麼是執行緒鎖?使用鎖的目的是什麼?先看一個例子。

複製程式碼

   private void testSimple(){
        SimpleRunner runner = new SimpleRunner();
        pool.execute(runner);
        pool.execute(runner);
    }
    int account01 =10;
    int account02 = 0;
    class SimpleRunner implements Runnable{
        @Override
        public void run() {
            while(true){//保證兩個賬戶的總額度不變
                account01 --;
                sleep(1000);
                account02 ++;
                Console.println("account01:"+account01+"  account02:"+account02);
            }
        }
    }
    private void sleep(int time){
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

複製程式碼

 呼叫testSimple()方法開啟兩個執行緒執行賬戶金額轉移,執行結果如下:

複製程式碼

account01:9  account02:2
account01:9  account02:2
account01:8  account02:4
account01:8  account02:4
account01:6  account02:6
account01:6  account02:6
account01:5  account02:7
account01:4  account02:8

複製程式碼

 很明顯兩個賬戶的金額總和無法保證為10,甚至變多了。之所以發生這種狀況一方面是因為++ 和--操作不是原子操作,其次兩個變數的修改也沒有保證同步進行。由於執行緒的不確定性則將導致資料嚴重混亂。下面換一種方式看看如何:

我們修改while迴圈體,不使用++或者--操作,同時對操作進行加鎖:

複製程式碼

 while(true){//保證兩個賬戶的總額度不變
                synchronized ("lock"){//通過synchronized鎖把兩個變數的修改進行同步
                    account01 = account01 -1;
                    account02 = account02 +1;
                    Console.println("account01:"+account01+"  account02:"+account02);
                    sleep(1000);
                }
            }

複製程式碼

 執行結果如下:

account01:9  account02:1
account01:8  account02:2
account01:7  account02:3
account01:6  account02:4
account01:5  account02:5

 現在資料就能夠完全正常了。這裡涉及到synchronized 鎖,其目的就是保證在任意時刻,只允許一個執行緒進行對臨界區資源(被鎖著的程式碼塊)的操作

習慣上喜歡稱這種機制為加鎖,為了容易理解,可以把這種機制理解為一把鑰匙和被鎖著的程式碼塊,只有拿到鑰匙的執行緒才能執行被鎖住的程式碼塊。而鑰匙就是synchronized(“lock”)中的字串物件"lock",而被鎖著的程式碼塊則是{}中的程式碼。

某個執行緒如果想要執行程式碼塊中的內容,則必須要擁有鑰匙"lock"物件。但“lock”有個特性,同一時刻只允許一個執行緒擁有(暫時不考慮共享鎖)。這樣就可以保證所有的執行緒依次執行被鎖著的程式碼塊,避免資料混亂。在這裡有一個前提條件,也就是鑰匙是對於所有執行緒可見的,應該設定為全域性變數且只有一個例項,否則每一個執行緒都有一個自己的鑰匙,那麼就起不到鎖的作用了。例如:

複製程式碼

            while(true){
                String lock = new String("lock");//每個執行緒進入run方法的時候都new一個自己的鑰匙
                synchronized (lock){
                    account01 = account01 -1;
                    account02 = account02 +1;
                    Console.println("account01:"+account01+"  account02:"+account02);
                    sleep(1000);
                }
            }

複製程式碼

 執行結果如下:

account01:8  account02:2
account01:8  account02:2
account01:6  account02:3
account01:6  account02:3
account01:5  account02:5
account01:4  account02:5

 這樣便又發生了混亂,每個執行緒都有自己的鑰匙,他們隨時都可以操作臨界區資源,和沒有加鎖無任何區別。所以在多執行緒操作中,鎖的使用至關重要!!!

 在java中有哪些鎖?該如何進行分類呢?

1、共享鎖/排它鎖 

    共享鎖和排他鎖是從同一時刻是否允許多個執行緒持有該鎖的角度來劃分。
              共享鎖允許同一時刻多個執行緒進入持有鎖,訪問臨界區資源。而排他鎖就是通常意義上的鎖,同一時刻只允許一個執行緒訪問臨界資源。對於共享鎖,主要是指對資料庫讀操作中的讀鎖,在讀寫資源的時候如果沒有執行緒持有寫鎖和請求寫鎖,則此時允許多個執行緒持有讀鎖。
              在這裡理解共享鎖的時候,不是任意時刻都允許多執行緒持有共享鎖的,而是在某些特殊情況下才允許多執行緒持有共享鎖,在某些情況下不允許多個執行緒持有共享鎖,否則,如果沒有前提條件任意時刻都允許執行緒任意持有共享鎖,則共享鎖的存在無意義的。例如讀寫鎖中的讀鎖,只有當沒有寫鎖和寫鎖請求的時候,就可以允許多個執行緒同時持有讀鎖。這裡的前提條件就是“沒有寫鎖和寫鎖請求”,而不是任意時刻都允許多執行緒持有共享讀鎖。
  2、悲觀鎖/樂觀鎖  
            主要用於資料庫資料的操作中,而對於執行緒鎖中較為少見。
            悲觀鎖和樂觀鎖是一種加鎖思想。對於樂觀鎖,在進行資料讀取的時候不會加鎖,而在進行寫入操作的時候會判斷一下資料是否被其它執行緒修改過,如果修改則更新資料,如果沒有則繼續進行資料寫入操作。樂觀鎖不是系統中自帶的鎖,而是一種資料讀取寫入思想。應用場景例如:在向資料庫中插入資料的時候,先從資料庫中讀取記錄修改版本標識欄位,如果該欄位沒有發生變化(沒有其他執行緒對資料進行寫操作)則執行寫入操作,如果發生變化則重新計算資料。
             對於悲觀鎖,無論是進行讀操作還是進行寫操作都會進行加鎖操作。對於悲觀鎖,如果併發量較大則比較耗費資源,當然保證了資料的安全性。

 3、可重入鎖/不可重入
                這兩個概念是從同一個執行緒在已經持有鎖的前提下能否再次持有鎖的角度來區分的。
                對於可重入鎖,如果該執行緒已經獲取到鎖且未釋放的情況下允許再次獲取該鎖訪問臨界區資源。此種情況主要是用在遞迴呼叫的情況下和不同的臨界區使用相同的鎖的情況下。
                對於不可重入鎖,則不允許同一執行緒在持有鎖的情況下再次獲取該鎖並訪問臨界區資源。對於不可重入鎖,使用的時候需要小心以免造成死鎖。

 4、公平鎖/非公平鎖
                這兩個概念主要使用執行緒獲取鎖的順序角度來區分的。
                對於公平鎖,所有等待的執行緒按照按照請求鎖的先後循序分別依次獲取鎖。
                對於非公平鎖,等待執行緒的執行緒獲取鎖的順序和請求的先後不是對應關係。有可能是隨機的獲取鎖,也有可能按照其他策略獲取鎖,總之不是按照FIFO的順序獲取鎖。
                在使用ReentrantLock的時候可以通過構造方法主動選擇是實現公平鎖還是非公平鎖。

5、自旋鎖/非自旋鎖
                這兩種概念是從執行緒等待的處理機制來區分的。
                自旋鎖在進行鎖請求等待的時候不進行wait掛起,不釋放CPU資源,執行while空迴圈。直至獲取鎖訪問臨界區資源。適用於等待鎖時間較短的情景,如果等待時間較長,則會耗費大量的CPU資源。而如果等待時間較短則可以節約大量的執行緒切換資源。
                非自旋鎖在進行鎖等待的時候會釋放CPU資源,可以通多sleep wait 或者CPU中斷切換上下文,切換該執行緒。線上程等待時間較長的情況下可以選擇此種實現機制。
        除此之外還有一種介於兩者之間的鎖機制——自適應自旋鎖。當執行緒進行等待的時候先進性自旋等待,在自旋一定時間(次數)之後如果依舊沒有持有鎖則掛起等待。在jvm中synchronized鎖已經使用該機制進行處理鎖等待的情況。
在工作中可以根據不同的情況選取合適的鎖進行使用。無論使用哪種鎖,其目的都是保證程式能夠按照要求順利執行,避免資料混亂情況的發生。

常用鎖的使用方法
        1、synchronized鎖:

    對於synchronized鎖首先需要明白加鎖的底層原理。每一個物件例項在物件頭中都會有monitor record列表記錄持有該鎖的執行緒,底層通多對該列表的查詢來判斷是否已經有執行緒在訪問臨界區資源。JVM內部細節之一:synchronized關鍵字及實現細節(輕量級鎖Lightweight Locking)

    在使用synchronized的時候必須弄清楚誰是“鑰匙”,屬於全域性變數還是執行緒內區域性變數,每個加鎖的臨界區是使用的哪個“鑰匙”物件。必須理清楚加鎖執行緒和“鑰匙”物件的關係!!!!

    synchronized只可以對方法和方法中的程式碼塊進行加鎖,而網上所說的“類鎖”並不是對類進行加鎖,而是synchronized(XXXX.class)。synchronized是不支援對類、構造方法和靜態程式碼塊進行加鎖的。

複製程式碼

     public synchronized void showInfo01(){//這裡synchronized鎖的是this物件,也即synchronized(this)
     }
    public void showInfo02(){
        synchronized (this){//這裡的this可以替換為任意Object物件。注意是Object物件,基本變數不行。java中字串是String例項,所以字串是可以的。
            //doSomething
        }
    }

複製程式碼

         2、reentranLock

    synchronized加鎖機制使基於JVM層面的加鎖,而ReentrantLock是基於jdk層面的加鎖機制。ReentrantLock根據名稱可以看出是可重入鎖,其提供的構造方法可以指定公平鎖或非公平鎖。ReentrantLock使用起來比synchronized更加靈活、方便和高效。

複製程式碼

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {//通過true或false來指定公平鎖或非公平鎖
        sync = fair ? new FairSync() : new NonfairSync();
    }

複製程式碼

 下面看一下使用方法:這裡使用的是預設非公平鎖進行測試。

複製程式碼

    private void testReentrantLock() {
        MyRunnerForReentrantLock run = new MyRunnerForReentrantLock();
        for (int i = 0; i < 10; i++) {//開啟10個執行緒進行測試
       sleep(10);//睡眠10ms保證執行緒開啟的順序能夠按照1-10依次開啟
            pool.execute(run);
        }
    }
    LockTest lockTest = new LockTest();
    class MyRunnerForReentrantLock implements Runnable {
        @Override
        public void run() {
            lockTest.reEnterLock(new AtomicInteger(3));//在run方法中呼叫reEnterLock()方法測試重入測試
        }
    }
    class LockTest {
        ReentrantLock reentrantLock = new ReentrantLock();//使用預設的非公平鎖ReentrantLock
        private void reEnterLock(AtomicInteger time) {
            reentrantLock.lock();//加鎖
            Console.println(Thread.currentThread().getName() + "--" + time);
            try {
                if (time.get() == 0) {
                    return;
                } else {
                    time.getAndDecrement();
                    reEnterLock(time);//這裡使用遞迴來測試重入
                }
            } finally {
                reentrantLock.unlock();//釋放鎖。注意這裡在finally中釋放鎖避免加鎖程式碼丟擲異常導致鎖無法釋放造成阻塞
            }
        }
}

複製程式碼

 執行結果如下,注意執行緒輸出的順序.

複製程式碼

pool-1-thread-1--3
pool-1-thread-1--2
pool-1-thread-1--1
pool-1-thread-1--0

pool-1-thread-2--3
pool-1-thread-2--2
pool-1-thread-2--1
pool-1-thread-2--0

pool-1-thread-4--3
pool-1-thread-4--2
pool-1-thread-4--1
pool-1-thread-4--0

pool-1-thread-5--3
pool-1-thread-5--2
pool-1-thread-5--1
pool-1-thread-5--0

pool-1-thread-8--3
......
......

複製程式碼

 可以看出同一個執行緒中time變數從3、2、1、0依次迴圈,說明執行緒進入了迴圈體,那麼執行緒確實是允許重入,同一個執行緒可以多次獲取該鎖。

但是注意以下執行緒輸出的順序卻不是由1到10.而是 pool-1-thread-1、pool-1-thread-2、pool-1-thread-4、pool-1-thread-5、pool-1-thread-8.這就是因為ReentrantLock使用的非公平鎖造成的,使用非公平鎖的執行緒在獲取“鑰匙”的順序上和執行緒開始等待的順序是沒有關係的。我們修改一下使用公平鎖測試一下:修改以下程式碼:

        ReentrantLock reentrantLock = new ReentrantLock(true);//使用公平鎖ReentrantLock

 執行結果如下:

複製程式碼

pool-1-thread-1--3
pool-1-thread-1--2
pool-1-thread-1--1
pool-1-thread-1--0
pool-1-thread-2--3
pool-1-thread-2--2
pool-1-thread-2--1
pool-1-thread-2--0
pool-1-thread-3--3
pool-1-thread-3--2
pool-1-thread-3--1
pool-1-thread-3--0
pool-1-thread-4--3
pool-1-thread-4--2
pool-1-thread-4--1
pool-1-thread-4--0
pool-1-thread-5--3
pool-1-thread-5--2
....
....

複製程式碼

 可以看出執行緒的執行順序按照1、2、3、4的順序進行輸出。

除了上面的lock()方法外ReentrantLock還提供了兩個過載的方法tryLock。ReentrantLock在進行等待持鎖的時候不同於synchronized之處就在於ReentrantLock可以中斷執行緒的等待,不再等待鎖。其主要方法就是tryLock()的使用。

  tryLock被過載了兩個方法,方法簽名為:

public boolean tryLock() {}
public boolean tryLock(long timeout, TimeUnit unit)throws InterruptedException {}
後者指定了等待超時時間

官方文件中註明了tryLock等待鎖的機制:

複製程式碼

public boolean tryLock()
Acquires the lock only if it is not held by another thread at the time of invocation.
Acquires the lock if it is not held by another thread and returns immediately with the value true, setting the lock hold count to one. Even when this lock has been set to use a fair ordering policy, a call to tryLock() will immediately acquire the lock if it is available, whether or not other threads are currently waiting for the lock. This "barging" behavior can be useful in certain circumstances, even though it breaks fairness. If you want to honor the fairness setting for this lock, then use tryLock(0, TimeUnit.SECONDS) which is almost equivalent (it also detects interruption).
If the current thread already holds this lock then the hold count is incremented by one and the method returns true.
If the lock is held by another thread then this method will return immediately with the value false.

複製程式碼

 

複製程式碼

public boolean tryLock(long timeout,  @NotNull TimeUnit unit) throws InterruptedException
Acquires the lock if it is not held by another thread within the given waiting time and the current thread has not been interrupted.
Acquires the lock if it is not held by another thread and returns immediately with the value true, setting the lock hold count to one. If this lock has been set to use a fair ordering policy then an available lock will not be acquired if any other threads are waiting for the lock. This is in contrast to the tryLock() method. If you want a timed tryLock that does permit barging on a fair lock then combine the timed and un-timed forms together:
       if (lock.tryLock() ||
          lock.tryLock(timeout, unit)) {
        ...
      }
If the current thread already holds this lock then the hold count is incremented by one and the method returns true.
If the lock is held by another thread then the current thread becomes disabled for thread scheduling purposes and lies dormant until one of three things happens:
The lock is acquired by the current thread; or
Some other thread interrupts the current thread; or
The specified waiting time elapses
If the lock is acquired then the value true is returned and the lock hold count is set to one.
If the current thread:
has its interrupted status set on entry to this method; or
is interrupted while acquiring the lock,
then InterruptedException is thrown and the current thread's interrupted status is cleared.
If the specified waiting time elapses then the value false is returned. If the time is less than or equal to zero, the method will not wait at all.
In this implementation, as this method is an explicit interruption point, preference is given to responding to the interrupt over normal or reentrant acquisition of the lock, and over reporting the elapse of the waiting time.

複製程式碼

 這裡有中文的翻譯:Java中Lock,tryLock,lockInterruptibly有什麼區別?(郭無心的回答)
       3、讀寫鎖的使用

    對於讀寫鎖的請求“鑰匙”策略如下:

        當寫鎖操作臨界區資源時,其它新過來的執行緒一律等待,無論是讀鎖還是寫鎖。

        當讀鎖操作臨界區資源時,如果有讀鎖請求資源可以立即獲取,不用等待;如果有寫鎖過來請求資源則需要等待讀鎖釋放之後才可獲取;如果有寫鎖在等待,然後又過來的有讀鎖,則讀鎖將會等待,寫鎖將會優先獲取臨界區資源操作許可權,這樣可以避免寫執行緒的長期等待。

使用方法如下:

複製程式碼

    private void testReentrantRWLock() {
        MyRunnerForReentrantRWLock run = new MyRunnerForReentrantRWLock();
        for (int i = 0; i < 10; i++) {//開啟10個執行緒測試

       sleep(10);//睡眠10ms保證執行緒開啟的順序能夠按照1-10依次開啟
            pool.execute(run);
        }
    }
    AtomicInteger num = new AtomicInteger(1);//用來切換讀寫鎖測試方法
    ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock(true);//公平讀寫鎖
    private class MyRunnerForReentrantRWLock implements Runnable {
        @Override
        public void run() {
            if(num.getAndIncrement() ==3){
                lockTest.write();//呼叫寫鎖測試
            }else{
                lockTest.read();//呼叫讀鎖測試
            }
        }
    }
        public void read() {//使用讀鎖
            rwlock.readLock().lock();
            try {
                Console.println(Thread.currentThread().getName()+"------read");
          sleep(2000);
            } finally {
                rwlock.readLock().unlock();
            }
        }
        public void write() {//使用寫鎖
            rwlock.writeLock().lock();
            try {
                sleep(2000);//模擬寫操作
                Console.println(Thread.currentThread().getName()+"------write");
            }finally {
                rwlock.writeLock().unlock();
            }
        }

複製程式碼

 執行結果如下:

複製程式碼

pool-1-thread-1------read
pool-1-thread-2------read
//在這裡有明顯的停頓,大約2s之後下面的直接輸出,沒有停頓
pool-1-thread-3------write
pool-1-thread-4------read
pool-1-thread-5------read
pool-1-thread-7------read
pool-1-thread-10------read
pool-1-thread-6------read
pool-1-thread-8------read
pool-1-thread-9------read

複製程式碼

 由執行結果執行順序和時間可以看出,在進行write的時候其它讀執行緒進行了等待操作,然後write釋放之後,其它讀操作同時操作臨界區資源,未發生阻塞等待。
        4、自旋鎖

    自旋鎖是線上程等待的時候通過自選while(){}空迴圈避免了執行緒掛起切換,減少了執行緒切換執行的時間。因此在選擇使用自旋鎖的時候儘量保證加鎖程式碼的執行時間小於等待時間,這樣就可以避免自旋鎖大量佔用CPU空轉,同時又免去了非自旋鎖執行緒切換的花銷。如果加鎖程式碼塊較多,此時自旋鎖就喲啊佔用太多的CPU進行空轉,此時如果發生大量執行緒請求鎖則會大量浪費資源。使用者可以根據具體情況來自定義自旋鎖的實現,可以實現公平自旋鎖和非公平自旋鎖。

這裡有介紹自定義自旋鎖的實現方式:Java鎖的種類以及辨析(二):自旋鎖的其他種類
    文章中介紹的很清楚了,TicketLock CLHLock 邏輯比較簡單,這裡不再詳述,只對MCSLock的實現做一下解讀。其中原文中MCSLock的實現unlock()方法中在釋放資源解鎖下一個等待執行緒的機制有些問題,已經做出了修改,請注意辨別。

複製程式碼

package com.zpj.thread.blogTest.lock;

/**
 * Created by PerkinsZhu on 2017/8/16 18:01.
 */

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

public class MCSLock {//這是通過連結串列實現對執行緒的控制的。每過來一個新的執行緒則把它新增到連結串列上阻塞進行while迴圈,當前一個執行緒結束之後,修改下一個執行緒的開關,開啟下個執行緒持有鎖。
    public static class MCSNode {
        volatile MCSNode next;
        volatile boolean isLocked = true;
    }
    private static final ThreadLocal<MCSNode> NODE = new ThreadLocal<MCSNode>();//這裡儲存的是當前執行緒的node,要理解ThreadLocal 的工作機制
    @SuppressWarnings("unused")
    private volatile MCSNode queue;
    private static final AtomicReferenceFieldUpdater<MCSLock, MCSNode> UPDATER = AtomicReferenceFieldUpdater.newUpdater(MCSLock.class, MCSNode.class, "queue");

    public void lock() {
        MCSNode currentNode = new MCSNode();//過來一個新執行緒建立一個node,同時防止在當前執行緒的NODE中進行儲存。
        NODE.set(currentNode);//注意,這裡的NODE儲存的資料各個執行緒中是不共享的
        MCSNode preNode = UPDATER.getAndSet(this, currentNode);//獲取前一個node節點,並更新當前節點
        if (preNode != null) {//前一個節點存在說明有執行緒正在操作臨界區資源。則當前執行緒迴圈等待
            preNode.next = currentNode;//把當前節點加入到連結串列中,等待獲取資源
            while (currentNode.isLocked) {}//迴圈等待,直至前一個執行緒釋放資源,修改當前node的isLocked標誌位
        }
    }

    public void unlock() {
        MCSNode currentNode = NODE.get();//取出當前執行緒的node節點
        if (currentNode.next == null) {//如果沒有新的執行緒等待持鎖
            if (UPDATER.compareAndSet(this, currentNode, null)) {//把當前node釋放,如果成功則結束,如果失敗進入else
            } else { //設定失敗說明突然有執行緒在請求臨界區資源進行等待。此時有新的執行緒更新了UPDATER資料。
        //***********************注意下面的邏輯,已經進行修改  【start】*********************************
                while (currentNode.next == null) {}//等待新加入的執行緒把節點加入連結串列
                // 此時currentNode.next != null 這裡理應使用鎖資源,而不應該直接結束,不然等待的執行緒無法獲取“鑰匙”訪問臨界區資源。所以新增以下兩行程式碼釋放鎖資源
                currentNode.next.isLocked = false;//釋放新新增執行緒的等待
                currentNode.next = null;
         //********************************** end  ******************************
            }
        } else {
            currentNode.next.isLocked = false;//釋放下一個等待鎖的執行緒
            currentNode.next = null;
        }
    }
}

複製程式碼

  5、訊號量實現鎖效果

  在jdk中,除了以上提供的Lock之外,還有訊號量Semaphore也可以實現加鎖特性。Semaphore是控制訪問臨界區資源的執行緒數量,Semaphore設定一個允許同時操作臨界區資源的閾值,如果請求的執行緒在閾值之內則允許所有執行緒同時訪問臨界區資源,如果超出設定的該閾值則掛起等待,直至有執行緒退出釋放之後,才允許新的資源獲得操作臨界區資源的權利。如果需要把它當做鎖使用,則只需要設定該閾值為1,即任意時刻只允許一個執行緒對臨界區資源進行操作即可。雖然不是鎖,但卻實現了鎖的功能——執行緒互斥序列。

使用示例:

複製程式碼

Semaphore semaphore = new Semaphore(1);//同時只允許一個執行緒可以訪問臨界區資源
    private void testSemaphore(){
        for(int i = 0; i<5;i++){//開啟5個執行緒競爭資源
            pool.execute(new SemapRunner());
        }
    }
    class SemapRunner implements Runnable{
        @Override
        public void run() {
            try {
                Console.println(Thread.currentThread().getName()+"  請求資源");
                semaphore.acquire();//請求資源
                Console.println(Thread.currentThread().getName()+"  獲取到資源");
                sleep(2000);
                Console.println(Thread.currentThread().getName()+"  釋放資源");
                semaphore.release();//釋放資源
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

複製程式碼

 執行結果如下:

複製程式碼

pool-1-thread-2  請求資源
pool-1-thread-4  請求資源
pool-1-thread-2  獲取到資源
pool-1-thread-5  請求資源
pool-1-thread-1  請求資源
pool-1-thread-3  請求資源
pool-1-thread-2  釋放資源
pool-1-thread-4  獲取到資源
pool-1-thread-4  釋放資源
pool-1-thread-5  獲取到資源
pool-1-thread-5  釋放資源
pool-1-thread-1  獲取到資源
pool-1-thread-1  釋放資源
pool-1-thread-3  獲取到資源
pool-1-thread-3  釋放資源

複製程式碼

 由結果可以看出,只有當一個執行緒釋放資源之後,才允許一個等待的資源獲取到資源,這樣便實現了類似加鎖的操作。

 

  在進行執行緒操作的過程中需要根據實際情況選取不同的鎖機制來對執行緒進行控制,以保證資料、執行邏輯的正確!!!無論是使用synchronized鎖還是使用jdk提供的鎖亦或自定義鎖,都要清晰明確使用鎖的最終目的是什麼,各種鎖的特性是什麼,使用場景分別是什麼?這樣才能夠線上程中熟練運用各種鎖。

 

 

=========================================

原文連結:多執行緒(五) java的執行緒鎖 轉載請註明出處!

=========================================