ReentrantLock入門級

是什麼

synchronized關鍵詞功能一致,讓線程保持同步。synchronized是基於Java語法上的實現,而ReentrantLock是基於API實現,操作上相對靈活。JDK1.7 之後 兩者的性能上不分秋色。ReentrantLock實現了Lock接口,我們來看看那Lock幾個主要接口定義:

/**
當前線程嘗試獲取獨佔鎖,如果發現鎖已被其他線程佔用將被掛起。
**/
void lock();

/**
同void lock() 方法類似,只不過該方法能夠被線程中斷。
**/
void lockInterruptibly() throws InterruptedException;

/**
當前線程調用該方法會嘗試獲取鎖,如果獲取到返回true,反之返回false,並不會阻塞該線程。
**/
boolean tryLock();
/**
同boolean tryLock()方法類似。也是嘗試獲取鎖,在拿不到鎖時會等待一定時間,等待時可以被中斷。超時之後還未拿到鎖返回false.
**/
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

/**
 釋放鎖。
**/
void unlock();

/**
 返回一個與lock實例綁定的Condition,通過調用Condition#await()方法,可以釋放當前線程持有的鎖並阻塞掛起。
**/
Condition newCondition();

Condition的作用主要應用於多線程的協同,下面看看Condition 接口的一些主要定義:

/**
 對於獲取鎖的線程,調用此方法後會阻塞直到其他線程喚醒或者線程被中斷
**/
void await() throws InterruptedException;

/**
相比較await()而言,它不響應中斷。
**/
void awaitUninterruptibly();
/**
喚醒因調用同一個condition實例的await() 方法 而阻塞掛起的線程
**/ 
void signal();

/**
 喚醒所有阻塞的線程
**/
void signalAll();

怎麼用

ReentrantLock鎖的基本使用

需求:有20個用戶(線程)同時去搶購剩餘的10張火車票,必須保證數據不出現混亂。具體實現如下:

TicketMachine

/**
 * 取票機
 */
public class TicketMachine {
    Lock lock = new ReentrantLock();
    /**
     * 票數量
     */
    private Integer ticketNum = 10;

    /**
     * 購買
     */
    public void buying() {
        try {
           // 1.
            lock.lock();
            if (ticketNum > 0) {
                System.out.println(Thread.currentThread().getName() + "購買了第" + ticketNum + "張票");

                ticketNum--;
            } else {
                System.out.println("已購買完,搶不到了.");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
           // 2.
            lock.unlock();
        }
    }
}

運行方法

 TicketMachine ticketMachine = new TicketMachine();
        for (Integer i = 0; i < 20; i++) {
            new Thread(() -> {
                ticketMachine.buying();
            }, "同學" + (i + 1)).start();
        }

運行結果

同學1購買了第10張票
同學4購買了第9張票
同學2購買了第8張票
同學3購買了第7張票
同學5購買了第6張票
同學6購買了第5張票
同學7購買了第4張票
同學8購買了第3張票
同學9購買了第2張票
同學10購買了第1張票
已購買完,搶不到了.
已購買完,搶不到了.
已購買完,搶不到了.
已購買完,搶不到了.
已購買完,搶不到了.
已購買完,搶不到了.

上面有幾處我們着重的說一下 。代碼(1)處調用lock() 方法獲取鎖保證同一時刻只能有一個線程對變量ticketNum(票數)進行操作,不會造成髒數據的問題。代碼(2)處將unlock()方法放置finally塊中 確保代碼執行完畢後鎖能進行釋放,不造成死鎖。

針對此案例我們提出幾個疑問

  • 若沒有調用lock()方法,直接調用 unLock() 方法會出現問題麼?
  • 若多次調用lock()方法,僅調用一次 unLock() 方法會出現問題麼?

問題一:若無調用lock() 方法,直接執行unLock() 方法將會拋出IllegalMonitorStateException。
問題二: 會導致其他線程獲取不到鎖。

Lock結合Condition 的使用

需求:10個線程每次以10增量值對變量sum進行求和運算,當任何一個線程發現求和運算得出的結果等於100,將喚醒另一個線程將結果輸出。

具體實現如下:

/**
 * Lock 和 Condition 配合使用案例
 */
public class LockConditionDemo {
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    /**
     * 總和
     */
    private Integer sum = 0;

    /**
     * 計算和
     *
     * @param count
     */
    public void calcSum(Integer count) {
        try {
            lock.lock();
            sum += count;
            //4.
            if (sum == 100) {
                condition.signal();
                System.out.println("sum 已等於 100 ,喚醒線程...");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    /**
     * 顯示總和
     */
    public void show() {
        try {
            lock.lock();
            System.out.println("線程【"+Thread.currentThread().getName()+"】"+"等待輸出sum和");
            //2.
            condition.await();

            System.out.println("線程【"+Thread.currentThread().getName()+"】"+"得出 sum = " + sum);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

運行方法:

    public static void main(String[] args) {
        LockConditionDemo t = new LockConditionDemo();
        //1.
        Thread thread = new Thread(() -> {
            t.show();
        }, "獲取求和線程");
        thread.start();

        //3.
        for (Integer i = 0; i < 10; i++) {
            new Thread(() -> t.calcSum(10), "線程" + i).start();
        }

    }

輸出結果:

線程【獲取求和線程】等待輸出sum和
sum 已等於 100 ,喚醒線程...
線程【獲取求和線程】得出 sum = 100

從代碼(1) 處,我們可以看出啓動了一個 獲取求和線程,代碼(2) 處,調用了condition.await()方法,將該線程阻塞掛起,等待其他線程喚醒,代碼(3) 處啓動了20個 對變量sum求和的線程,緊接着代碼(4)處對sum的值進行判斷,如果等於100將調用 condition.signal(),喚醒因調用同一個condition實例的await() 方法 而阻塞掛起的線程。

書寫技術文章是一個循序漸進的過程,所以我不能保證每句話、每行代碼都是對的,但至少能保證不復制、不粘貼,每篇文章都是自己對技術的認識、細心斟酌總結出來的。喬布斯說:我們在這個星球上的時間都很短,很少有機會去做幾件真正偉大的事情,同時要做得好,我必須要趁我還年輕的時候完成這些事。
其實我想說的是,我是一枚程序員,我只想在有限的時間內儘可能去沉澱我這一生中所能沉澱下來的東西

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章