中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java并發中ReentrantLock鎖怎么用

發布時間:2021-11-29 09:12:17 來源:億速云 閱讀:151 作者:iii 欄目:開發技術

這篇文章主要講解了“Java并發中ReentrantLock鎖怎么用”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Java并發中ReentrantLock鎖怎么用”吧!

    重入鎖可以替代關鍵字 synchronized 。

    在 JDK5.0 的早期版本中,重入鎖的性能遠遠優于關鍵字 synchronized ,

    但從 JDK6.0 開始, JDK 在關鍵字 synchronized 上做了大量的優化,使得兩者的性能差距并不大。

    重入鎖使用 ReentrantLock 實現

    1、重入鎖

    package com.shockang.study.java.concurrent.lock;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ReentrantLockDemo implements Runnable {
        public static ReentrantLock lock = new ReentrantLock();
        public static int i = 0;
    
        @Override
        public void run() {
            for (int j = 0; j < 10000000; j++) {
                lock.lock();
                lock.lock();
                try {
                    i++;
                } finally {
                    lock.unlock();
                    lock.unlock();
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            ReentrantLockDemo tl = new ReentrantLockDemo();
            Thread t1 = new Thread(tl);
            Thread t2 = new Thread(tl);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println(i);
        }
    }

    控制臺打印

    20000000

    說明

    一個線程連續兩次獲得同一把鎖是允許的。

    如果不允許這么操作,那么同一個線程在第 2 次獲得鎖時,將會和自己產生死鎖。

    程序就會“卡死”在第 2 次申請鎖的過程中。

    但需要注意的是,如果同一個線程多次獲得鎖,那么在釋放鎖的時候,也必須釋放相同次數。

    如果釋放鎖的次數多了,那么會得到一個 java.lang.IllegalMonitorStateException 異常,反之,如果釋放鎖的次數少了,那么相當于線程還持有這個鎖,因此,其他線程也無法進入臨界區。

    2、中斷響應

    對于關鍵字 synchronized 來說,如果一個線程在等待鎖,那么結果只有兩種情況,要么它獲得這把鎖繼續執行,要么它就保持等待。

    而使用重入鎖,則提供另外一種可能,那就是線程可以被中斷。

    也就是在等待鎖的過程中,程序可以根據需要取消對鎖的請求。

    有些時候,這么做是非常有必要的。

    比如,你和朋友約好一起去打球,如果你等了半個小時朋友還沒有到,你突然接到一個電話,說由于突發情況,朋友不能如約前來了,那么你一定掃興地打道回府了。

    中斷正是提供了一套類似的機制。

    如果一個線程正在等待鎖,那么它依然可以收到一個通知,被告知無須等待,可以停止工作了。

    這種情況對于處理死鎖是有一定幫助的。

    下面的代碼產生了一個死鎖,但得益于鎖中斷,我們可以很輕易地解決這個死鎖。

    package com.shockang.study.java.concurrent.lock;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    public class IntLock implements Runnable {
        public static ReentrantLock lock1 = new ReentrantLock();
        public static ReentrantLock lock2 = new ReentrantLock();
        int lock;
    
        /**
         * 控制加鎖順序,方便構造死鎖
         *
         * @param lock
         */
        public IntLock(int lock) {
            this.lock = lock;
        }
    
        @Override
        public void run() {
            try {
                if (lock == 1) {
                    lock1.lockInterruptibly();
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                    }
                    lock2.lockInterruptibly();
                } else {
                    lock2.lockInterruptibly();
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                    }
                    lock1.lockInterruptibly();
                }
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (lock1.isHeldByCurrentThread())
                    lock1.unlock();
                if (lock2.isHeldByCurrentThread())
                    lock2.unlock();
                System.out.println(Thread.currentThread().getId() + ":線程退出");
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            IntLock r1 = new IntLock(1);
            IntLock r2 = new IntLock(2);
            Thread t1 = new Thread(r1);
            Thread t2 = new Thread(r2);
            t1.start();
            t2.start();
            Thread.sleep(1000);
            //中斷其中一個線程
            t2.interrupt();
        }
    }

    控制臺輸出

    java.lang.InterruptedException

    at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)

    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)

    at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)

    at com.shockang.study.java.concurrent.lock.IntLock.run(IntLock.java:35)

    at java.lang.Thread.run(Thread.java:748)

    11:線程退出

    12:線程退出

    說明

    線程 t1 和 t2 啟動后, t1 先占用 lock1 ,再占用 lock2。

    t2 先占用 lock2 ,再請求 lock1。

    因此,很容易形成 t1 和 t2 之間的相互等待。

    在這里,對鎖的請求,統一使用 lockInterruptibly() 方法。

    這是一個可以對中斷進行響應的鎖申請動作,即在等待鎖的過程中,可以響應中斷。

    在代碼第 56 行,主線程 main 處于休眠狀態,此時,這兩個線程處于死鎖的狀態。

    在代碼第 58 行,由于 t2 線程被中斷,故 t2 會放棄對 lock1 的申請,同時釋放已獲得的 lock2 。

    這個操作導致 t1 線程可以順利得到 lock2 而繼續執行下去。

    3、鎖申請等待限時

    除了等待外部通知之外,要避免死鎖還有另外一種方法,那就是限時等待。

    依然以約朋友打球為例,如果朋友退退不來,又無法聯系到他,那么在等待 1 到 2 個小時后,我想大部分人都會掃興離去。

    對線程來說也是這樣。

    通常,我們無法判斷為什么一個線程退遲拿不到鎖。

    也許是因為死鎖了,也許是因為產生了饑餓。

    如果給定一個等待時間,讓線程自動放棄,那么對系統來說是有意義的。

    我們可以使用 tryLock() 方法進行一次限時的等待。

    tryLock(long, TimeUnit)

    下面這段代碼展示了限時等待鎖的使用。

    package com.shockang.study.java.concurrent.lock;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class TimeLock implements Runnable {
        public static ReentrantLock lock = new ReentrantLock();
    
        @Override
        public void run() {
            try {
                if (lock.tryLock(5, TimeUnit.SECONDS)) {
                    Thread.sleep(6000);
                } else {
                    System.out.println("get lock failed");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public static void main(String[] args) {
            TimeLock tl = new TimeLock();
            Thread t1 = new Thread(tl);
            Thread t2 = new Thread(tl);
            t1.start();
            t2.start();
        }
    }

    控制臺打印

    get lock failed
    Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
    at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
    at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
    at com.shockang.study.java.concurrent.lock.TimeLock.run(TimeLock.java:20)
    at java.lang.Thread.run(Thread.java:748)

    說明

    在這里, tryLock() 方法接收兩個參數,一個表示等待時長,另外一個表示計時單位。

    這里的單位設置為秒,時長為 5 ,表示線程在這個鎖請求中最多等待 5 秒。

    如果超過 5 秒還沒有得到鎖,就會返回 false 。

    如果成功獲得鎖,則返回 true 。

    在本例中,由于占用鎖的線程會持有鎖長達 6 秒,故另一個線程無法在 5 秒的等待時間內獲得鎖,因此請求鎖會失敗。

    tryLock()

    ReentrantLock.tryLock() 方法也可以不帶參數直接運行。

    在這種情況下,當前線程會嘗試獲得鎖,如果鎖并未被其他線程占用,則申請鎖會成功,并立即返回 true 。

    如果鎖被其他線程占用,則當前線程不會進行等待,而是立即返回 false 。

    這種模式不會引起線程等待,因此也不會產生死鎖。

    package com.shockang.study.java.concurrent.lock;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    public class TryLock implements Runnable {
        public static ReentrantLock lock1 = new ReentrantLock();
        public static ReentrantLock lock2 = new ReentrantLock();
        int lock;
    
        public TryLock(int lock) {
            this.lock = lock;
        }
    
        @Override
        public void run() {
            if (lock == 1) {
                while (true) {
                    if (lock1.tryLock()) {
                        try {
                            try {
                                Thread.sleep(500);
                            } catch (InterruptedException e) {
                            }
                            if (lock2.tryLock()) {
                                try {
                                    System.out.println(Thread.currentThread()
                                            .getId() + ":My Job done");
                                    return;
                                } finally {
                                    lock2.unlock();
                                }
                            }
                        } finally {
                            lock1.unlock();
                        }
                    }
                }
            } else {
                while (true) {
                    if (lock2.tryLock()) {
                        try {
                            try {
                                Thread.sleep(500);
                            } catch (InterruptedException e) {
                            }
                            if (lock1.tryLock()) {
                                try {
                                    System.out.println(Thread.currentThread()
                                            .getId() + ":My Job done");
                                    return;
                                } finally {
                                    lock1.unlock();
                                }
                            }
                        } finally {
                            lock2.unlock();
                        }
                    }
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            TryLock r1 = new TryLock(1);
            TryLock r2 = new TryLock(2);
            Thread t1 = new Thread(r1);
            Thread t2 = new Thread(r2);
            t1.start();
            t2.start();
        }
    }

    控制臺輸出

    11:My Job done
    12:My Job done

    說明

    上述代碼采用了非常容易死鎖的加鎖順序。

    也就是先讓 t1 獲得 lock1 ,再讓 2 獲得 lock2 ,接著做反向請求,讓 t1 申請 lock2 , t2 申請 lock1 。

    在一般情況下,這會導致 t1 和 2 相互等待。

    待,從而引起死鎖。

    但是使用 tryLock() 方法后,這種情況就大大改善了。

    由于線程不會傻傻地等待,而是不停地嘗試,因此,只要執行足夠長的時間,線程總是會得到所有需要的資源,從而正常執行(這里以線程同時獲得 lock1 和 lock2 兩把鎖,作為其可以正常執行的條件)。

    在同時獲得 lock1 和 lock2 后,線程就打印出標志著任務完成的信息“ My Job done”。

    4、公平鎖

    在大多數情況下,鎖的申請都是非公平的。

    也就是說,線程 1 首先請求了鎖 A ,接著線程 2 也請求了鎖 A 。

    那么當鎖 A 可用時,是線程 1 可以獲得鎖還是線程 2 可以獲得鎖呢?

    這是不一定的,系統只是會從這個鎖的等待隊列中隨機挑選一個。

    因此不能保證其公平性。

    這就好比買票不排隊,大家都圍在售票窗口前,售票員忙得焦頭爛額,也顧不及誰先誰后,隨便找個人出票就完事了。

    而公平的鎖,則不是這樣,它會按照時間的先后順序,保證先到者先得,后到者后得。

    公平鎖的一大特點是:它不會產生饑餓現象

    關于線程饑餓請參考我的博客——死鎖、活鎖和饑餓是什么意思?

    只要你排隊,最終還是可以等到資源的。

    如果我們使用 synchronized 關鍵字進行鎖控制,那么產生的鎖就是非公平的。

    而重入鎖允許我們對其公平性進行設置。

    它的構造函數如下:

    /**
     * 使用給定的公平策略創建一個 ReentrantLock 的實例。
     *
     * @param fair 如果此鎖應使用公平排序策略為 true
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

    當參數 fair 為 true 時,表示鎖是公平的。

    公平鎖看起來很優美,但是要實現公平鎖必然要求系統維護一個有序隊列,因此公平鎖的實現成本比較高,性能卻非常低下,因此,在默認情況下,鎖是非公平的。

    如果沒有特別的需求,則不需要使用公平鎖。

    公平鎖和非公平鎖在線程調度表現上也是非常不一樣的。

    下面的代碼可以很好地突出公平鎖的特點。

    package com.shockang.study.java.concurrent.lock;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    public class FairLock implements Runnable {
        public static ReentrantLock fairLock = new ReentrantLock(true);
    
        @Override
        public void run() {
            while (true) {
                try {
                    fairLock.lock();
                    System.out.println(Thread.currentThread().getName() + " 獲得鎖");
                } finally {
                    fairLock.unlock();
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            FairLock r1 = new FairLock();
            Thread t1 = new Thread(r1, "Thread_t1");
            Thread t2 = new Thread(r1, "Thread_t2");
            t1.start();
            t2.start();
        }
    }

    控制臺輸出

    獲得鎖

    Thread_t2 獲得鎖

    Thread_t2 獲得鎖

    Thread_t2 獲得鎖

    Thread_t2 獲得鎖

    Thread_t1 獲得鎖

    Thread_t1 獲得鎖

    Thread_t2 獲得鎖

    Thread_t2 獲得鎖

    Thread_t2 獲得鎖

    Thread_t1 獲得鎖

    Thread_t1 獲得鎖

    # 省略

    說明

    由于代碼會產生大量輸出,這里只截取部分進行說明。

    在這個輸出中,很明顯可以看到,兩個線程基本上是交替獲得鎖的,幾乎不會發生同一個線程連續多次獲得鎖的可能,從而保證了公平性。

    如果設置了 false,則會根據系統的調度,一個線程會傾向于再次獲取已經持有的鎖,這種分配方式是高效的,但是無公平性可言。

    源碼(JDK8)

    /**
     * 一種可重入互斥鎖,其基本行為和語義與使用同步方法和語句訪問的隱式監視鎖(即 synchronized)相同,但具有擴展功能。 
     * 
     * 可重入鎖屬于上次成功鎖定但尚未解鎖它的線程。
     * 
     * 當鎖不屬于另一個線程時,調用鎖的線程將返回,并成功獲取鎖。
     * 
     * 如果當前線程已經擁有鎖,則該方法將立即返回。這可以使用 isHeldByCurrentThread 和 getHoldCount 方法進行檢查。 
     * 
     * 此類的構造函數接受可選的公平性參數。
     * 
     * 當設置為 true 時,在競爭狀態下,鎖有利于向等待時間最長的線程授予訪問權限。否則,此鎖不保證任何特定的訪問順序。
     * 
     * 使用由多線程訪問的公平鎖的程序可能顯示較低的總吞吐量
     * 
     * (即,較慢;通常比使用默認設置的要慢得多,但是在獲得鎖和保證不饑餓的時間上有較小的差異。
     * 
     * 但是請注意,鎖的公平性并不能保證線程調度的公平性。
     * 
     * 因此,使用公平鎖的多個線程中的一個線程可以連續多次獲得公平鎖,而其他活動線程則沒有進行并且當前沒有持有該鎖。
     * 
     * 還要注意,untimed tryLock() 方法不支持公平性設置。
     * 
     * 如果鎖可用,即使其他線程正在等待,它也會成功。 
     * 
     * 建議的做法是總是在調用之后立即使用try塊鎖定,最典型的是在構建之前/之后,例如:
     * 
     * class X {
     *   private final ReentrantLock lock = new ReentrantLock();
     *   // ...
     *
     *   public void m() {
     *     lock.lock();  // block until condition holds
     *     try {
     *       // ... method body
     *     } finally {
     *       lock.unlock()
     *     }
     *   }
     * }}
     * 
     * 除了實現鎖接口之外,這個類還定義了許多公共和受保護的方法來檢查鎖的狀態。
     * 
     * 其中一些方法只對 instrumentation 和 monitoring 有用。 
     * 
     * 此類的序列化與內置鎖的行為相同:反序列化的鎖處于未鎖定狀態,而與序列化時的狀態無關。 
     * 
     * 此鎖最多支持同一線程的2147483647個遞歸鎖。嘗試超過此限制會導致鎖定方法拋出錯誤。
     * 
     * @since 1.5
     * @author Doug Lea
     */
    public class ReentrantLock implements Lock, java.io.Serializable

    感謝各位的閱讀,以上就是“Java并發中ReentrantLock鎖怎么用”的內容了,經過本文的學習后,相信大家對Java并發中ReentrantLock鎖怎么用這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    信丰县| 南投市| 疏勒县| 江达县| 永兴县| 化德县| 黄山市| 鄂温| 乌拉特前旗| 仲巴县| 甘谷县| 东光县| 云霄县| 阿合奇县| 塔城市| 大厂| 尼木县| 罗甸县| 麻栗坡县| 自治县| 和硕县| 汾阳市| 青龙| 美姑县| 桓台县| 温州市| 饶河县| 文安县| 汉源县| 海安县| 西乡县| 哈尔滨市| 龙陵县| 大同市| 广昌县| 肃南| 安图县| 攀枝花市| 娱乐| 金湖县| 民县|