Java - 可重入鎖ReentrantLock簡單用法

Java - 可重入鎖ReentrantLock簡單用法

Java 中顯示鎖的借口和類主要位于java.util.concurrent.locks下,其主要的接口和類有:

  • 鎖接口Lock,其主要實現(xiàn)為ReentrantLock
  • 讀寫鎖接口ReadWriteLock,其主要實現(xiàn)為ReentrantReadWriteLock

一撒璧、接口Lock

其中顯示鎖Lock的定義為:

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

其中:

  1. lock()/unlock() : 為獲取鎖和釋放鎖的方法,其中l(wèi)ock()會阻塞程序盯漂,直到成功的獲取鎖。
  2. lockInterruptibly():與lock()不同的地方是储矩,它可以響應(yīng)程序中斷,如果被其他程序中斷了褂乍,則拋出InterruptedException持隧。
  3. tryLock():嘗試獲取鎖,該方法會立即返回逃片,并不會阻塞程序屡拨。如果獲取鎖成功則返回true,反之則返回false题诵。
  4. tryLock(long time, TimeUnit unit):嘗試獲取鎖洁仗,如果能獲取鎖則直接返回true;否則阻塞等待性锭,阻塞時長由傳入的參數(shù)來決定赠潦,在等待的同時響應(yīng)程序中斷,如果發(fā)生了中斷則拋出InterruptedException草冈;如果在等待的時間中獲取了鎖則返回true她奥,反之返回false。
  5. newCondition():新建一個條件怎棱,一個Lock可以關(guān)聯(lián)多個條件哩俭。

相比synchronized,顯示鎖可以用非阻塞的方式獲取鎖拳恋,可以響應(yīng)程序中斷凡资,可以設(shè)定程序的阻塞時間,擁有更加靈活的操作谬运。

二隙赁、可重入鎖ReentrantLock

2.1 基本用法

ReentrantLock是Lock接口的主要實現(xiàn)類,其基本用法lock()/unlock()實現(xiàn)了與synchronized一樣的語義梆暖,其中包括:

  • 可重入伞访,一個線程在持有一個鎖的前提下,可以繼續(xù)獲得該鎖轰驳;
  • 可以解決競態(tài)條件問題(臨界區(qū)資源)厚掷;
  • 可以保證內(nèi)存可見性問題。

ReentrantLock有兩個構(gòu)造方法级解。

public ReentrantLock()
public ReentrantLock(boolean fair)

參數(shù)fair表示是否保證公平冒黑,在不指定的情況下默認值為false,表示不保證公平勤哗。

公平的意思是指:等待時間最長的線程優(yōu)先獲取鎖薛闪。

但是保證公平可能會影響程序的性能,在一般情況下也不需要保證公平俺陋,所以默認值為 false 豁延。而synchronized也是不保證公平的昙篙。

在使用顯示鎖的情況下,一定要記得調(diào)用 unlock 诱咏。一般而言苔可,應(yīng)該將 lock 之后的代碼塊包裝在 try 語句中,在 finally 語句中釋放鎖袋狞,例如以下實現(xiàn)計數(shù)器的代碼:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by Joe on 2018/4/10.
 */
public class Counter {
    private final Lock lock = new ReentrantLock();
    private volatile int count;
    public void incr() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
    public int getCount() {
        return count;
    }
}

2.2 使用tryLock避免死鎖

使用tryLock()方法可以避免死鎖的發(fā)生焚辅。在持有一個鎖而嘗試獲取另外一個鎖,但是獲取不到的時候苟鸯,可以釋放已持有的鎖同蜻,給其他線程獲取鎖的機會,然后重試獲取所有的鎖早处。

接下來使用銀行之間轉(zhuǎn)賬的例子壤蚜。

表示賬戶的Account類:

public class Account {
    private Lock lock = new ReentrantLock();
    private volatile double money;
    public Account(double initialMoney) {
        this.money = initialMoney;
    }
    public void add(double money) {
        lock.lock();
        try {
            this.money += money;
        } finally {
            lock.unlock();
        }
    }
    public void reduce(double money) {
        lock.lock();
        try {
            this.money -= money;
        } finally {
            lock.unlock();
        }
    }
    public double getMoney() {
        return money;
    }
    void lock() {
        lock.lock();
    }
    void unlock() {
        lock.unlock();
    }
    boolean tryLock() {
        return lock.tryLock();
    }
}

Account類中的money表示當前的余額绵咱。add/reduce用于修改余額佛寿。在賬戶之間轉(zhuǎn)賬壶熏,需要這兩個賬戶都要進行鎖定。如果我們直接只用 lock() 咸包,我們的代碼清單如下:

public class AccountMgr {
    public static class NoEnoughMoneyException extends Exception {}
    public static void transfer(Account from, Account to, double money)
            throws NoEnoughMoneyException {
        from.lock();
        try {
            to.lock();
            try {
                if(from.getMoney() >= money) {
                    from.reduce(money);
                    to.add(money);
                } else {
                    throw new NoEnoughMoneyException();
                }
            } finally {
                to.unlock();
            }
        } finally {
            from.unlock();
        }
    }
}

但是這種寫法容易發(fā)生死鎖桃序。比如,兩個賬戶都想同時給對方進行轉(zhuǎn)賬烂瘫,并且均獲得了第一個鎖媒熊。在這種情況下就會發(fā)生死鎖。

接下來的代碼用于模擬賬戶轉(zhuǎn)賬的死鎖過程坟比。

public static void simulateDeadLock() {
    final int accountNum = 10;
    final Account[] accounts = new Account[accountNum];
    final Random rnd = new Random();
    for(int i = 0; i < accountNum; i++) {
        accounts[i] = new Account(rnd.nextInt(10000));
    }
    int threadNum = 100;
    Thread[] threads = new Thread[threadNum];
    for(int i = 0; i < threadNum; i++) {
        threads[i] = new Thread() {
            public void run() {
                int loopNum = 100;
                for(int k = 0; k < loopNum; k++) {
                    int i = rnd.nextInt(accountNum);
                    int j = rnd.nextInt(accountNum);
                    int money = rnd.nextInt(10);
                    if(i != j) {
                        try {
                            transfer(accounts[i], accounts[j], money);
                            System.out.println(i + "--->" + j + "轉(zhuǎn)賬成功:" + money);
                        } catch (NoEnoughMoneyException e) {
                        }
                    }
                }
            }
        };
        threads[i].start();
    }
}

public static void main(String[] args) {
    simulateDeadLock();
}

以上代碼創(chuàng)建了10個賬戶泛释,100個線程,每個線程均循環(huán)100次温算,在循環(huán)中隨機挑選兩個賬戶進行轉(zhuǎn)賬。在程序運行多次之后你會發(fā)現(xiàn)如下圖所示的情況间影,程序因為發(fā)生死鎖陷入阻塞態(tài)注竿,無法完整執(zhí)行程序:

<center>
死鎖.png-29.3kB
死鎖.png-29.3kB
</center>

接下來我們使用 tryLock 書寫一個新的方法,代碼如下所示:

public static boolean tryTransfer(Account from, Account to, double money)
            throws NoEnoughMoneyException {
    if (from.tryLock()) {
        try {
            if (to.tryLock()) {
                try {
                    if (from.getMoney() >= money) {
                        from.reduce(money);
                        to.add(money);
                    } else {
                        throw new NoEnoughMoneyException();
                    }
                    return true;
                } finally {
                    to.unlock();
                }
            }
        } finally {
            from.unlock();
        }
    }
    return false;
}

嘗試獲取賬戶的鎖魂贬,如果兩個鎖都能獲取成功巩割,則返回 true,反之則返回 false付燥。無論鎖的獲取狀態(tài)如何宣谈,在方法體結(jié)束之后都會釋放所有的鎖。同時我們可以改造 transfer 方法來循環(huán)調(diào)用該方法以避免死鎖情況的發(fā)生键科,其代碼可以為:

public static void transfer(Account from, Account to, double money)
            throws NoEnoughMoneyException {
    boolean success = false;
    do {
        success = tryTransfer(from, to, money);
        if (!success) {
            Thread.yield();
        }
    } while (!success);
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末闻丑,一起剝皮案震驚了整個濱河市漩怎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嗦嗡,老刑警劉巖勋锤,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異侥祭,居然都是意外死亡叁执,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門矮冬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谈宛,“玉大人,你說我怎么就攤上這事胎署∵郝迹” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵硝拧,是天一觀的道長径筏。 經(jīng)常有香客問我,道長障陶,這世上最難降的妖魔是什么滋恬? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮抱究,結(jié)果婚禮上恢氯,老公的妹妹穿的比我還像新娘。我一直安慰自己鼓寺,他們只是感情好勋拟,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著妈候,像睡著了一般敢靡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上苦银,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天啸胧,我揣著相機與錄音,去河邊找鬼幔虏。 笑死纺念,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的想括。 我是一名探鬼主播陷谱,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼瑟蜈!你這毒婦竟也來了烟逊?” 一聲冷哼從身側(cè)響起渣窜,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎焙格,沒想到半個月后图毕,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡眷唉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年予颤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冬阳。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡蛤虐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出肝陪,到底是詐尸還是另有隱情驳庭,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布氯窍,位于F島的核電站饲常,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏狼讨。R本人自食惡果不足惜贝淤,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望政供。 院中可真熱鬧播聪,春花似錦、人聲如沸布隔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽衅檀。三九已至招刨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間哀军,已是汗流浹背沉眶。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留排苍,地道東北人。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓学密,卻偏偏與公主長得像淘衙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子腻暮,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容