Java多線程之synchronized加強版——ReentrantLock

接下來介紹比synchronized功能上更豐富的關鍵字:重入鎖java

  • 靈活性:安全

    public class ReentrantLockTest implements Runnable{
        public static ReentrantLock lock = new ReentrantLock();
        public static int flag = 0;
    
        @Override
        public void run() {
            for (int i = 0; i < 10000000; i++) {
                lock.lock();
                try {
                    flag++;
                } finally {
                    lock.unlock();
                }
            }
        }
    
        public static void main(String args[]) throws InterruptedException {
            ReentrantLockTest test = new ReentrantLockTest();
            Thread first = new Thread(test);
            Thread second = new Thread(test);
            first.start();
            second.start();
            first.join();
            second.join();
            System.out.println(flag);
        }
    }
    複製代碼

    lock.lock();這裏,經過重入鎖保護臨界區安全,以避免發生線程安全問題。bash

    lock.unlock();這裏,必須手動指示釋放鎖的操做,不然其餘線程將沒法得到。ide

    在這段代碼裏,咱們能見到重入鎖靈活的特色。但爲何叫「重入」呢?函數

    看下段代碼:性能

    @Override
        public void run() {
            for (int i = 0; i < 10000000; i++) {
                lock.lock();
                lock.lock();
                try {
                    flag++;
                } finally {
                    lock.unlock();
                    lock.unlock();
                }
            }
        }
    複製代碼

    由於該鎖能反覆進進出出。但要注意一下:ui

    在上段代碼中,鎖是能夠重複獲取的。若是不容許,則該線程在第二次獲取鎖時會和本身產生死鎖問題。同時也要注意,線程獲取多少次鎖就要釋放多少此鎖。當獲取鎖的次數大於釋放鎖的次數、至關於該線程還持有鎖。當獲取鎖的次數少於釋放鎖的次數、則會獲得一個java.lang.IllegalMonitorStateException異常。this

  • 中斷響應:spa

    二話不說貼代碼:線程

    public class ReentrantLockTest implements Runnable{
        public static ReentrantLock producer = new ReentrantLock();
        public static ReentrantLock consumer = new ReentrantLock();
        public int flag = 0;
        public ReentrantLockTest(int flag){
            this.flag = flag;
        }
        @Override
        public void run() {
            try {
                if (flag == 0) {
                    producer.lockInterruptibly();
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
    
                    }
                    consumer.lockInterruptibly();
                    System.out.println(Thread.currentThread().getName() + "完成工做");
                } else {
                    consumer.lockInterruptibly();
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
    
                    }
                    producer.lockInterruptibly();
                    System.out.println(Thread.currentThread().getName() + "完成工做");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (producer.isHeldByCurrentThread()) {
                    producer.unlock();
                }
                if (consumer.isHeldByCurrentThread()) {
                    consumer.unlock();
                }
                System.out.println(Thread.currentThread().getName() + ": 線程退出");
            }
        }
    
        public static void main(String args[]) throws InterruptedException {
            ReentrantLockTest producerThread = new ReentrantLockTest(1);
            ReentrantLockTest consumerThread = new ReentrantLockTest(0);
            Thread first = new Thread(producerThread);
            Thread second = new Thread(consumerThread);
            first.setName("producer");
            second.setName("consumer");
            first.start();
            second.start();
            Thread.sleep(1000);
            second.interrupt();
        }
    }
    複製代碼

    這是一段容易形成死鎖的代碼,具體緣由你們應該懂。當執行到second.interrupt();時,second線程在等待鎖時被中斷,故second線程會放棄對鎖的申請、並對已持有資源進行釋放。first線程則可以正常獲取所等待的鎖並繼續執行下去。

    結果以下:

    producer完成工做
            java.lang.InterruptedException
            consumer: 線程退出
            producer: 線程退出
        	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 blog.ReentrantLockTest.run(ReentrantLockTest.java:22)
        	at java.lang.Thread.run(Thread.java:748)
    複製代碼

    真正完成工做的只有producer線程。

  • 限時等待:

    除了用中斷避免死鎖問題外,還能夠用限時等待鎖來避免。限時等待鎖有點像是系統自動完成線程中斷的感受。先展現下限時等待鎖的使用:

    public class showWait 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(Thread.currentThread().getName() + " get lock failed");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String args[]) {
            showWait test = new showWait();
            Thread t1 = new Thread(test);
            Thread t2 = new Thread(test);
            t1.setName("producer");
            t2.setName("consumer");
            t1.start();
            t2.start();
        }
    }
    複製代碼

    上述代碼展現了lock.tryLock(5,TimeUnit.SECONDS);的使用,在這裏,該方法接收兩個參數,分別是時長和計時單位。

    該方法也能夠不帶參數,當不帶參數時,當前線程會嘗試獲取鎖,若是鎖未被其餘線程佔有則會申請成功並當即返回true。若是鎖被其餘線程佔用則當即返回false。這種方法不會引發線程等待,因此不會產生死鎖問題。

  • 公平鎖:

    在多大數狀況下,鎖的申請都是非公平性的,有時會形成線程飢餓問題。當咱們使用synchronized時產生的鎖是非公平性的,但咱們使用ReentrantLock時能夠經過構造函數進行指定其公平性。 public ReentrantLock(boolean fair)

    當參數爲true時爲公平鎖,默認爲非公平鎖。公平鎖看起來挺優美的,但其必然要維護一個等待隊列,其性能必然會下降

  • 整理:

    • lock()
    • lockInterruptibly()
    • tryLock()
    • tryLock(long time,TimeUnit unit)
    • unlock()

    你們回顧下這幾個方法吧。

相關文章
相關標籤/搜索