ReentrantLock

  在jdk5.0以前AQS框架下的鎖的性能是遠遠超過synchronized的,從jdk6.0開始對synchronized作出優化後兩者在性能上差別已經不大了。ReentrantLock的有點在於:java

  • 靈活性。加鎖解鎖的過程是可控的,synchronized加鎖解鎖過程是編譯完成後JVM來實現的
  • 可響應中斷。synchronized下沒法得到鎖的線程是BLOCKED的,不能夠相應中斷。AQS下全部的鎖包括重入鎖沒法得到鎖是WAITING的,可相應中斷,能夠用來解決死鎖問題
  • 超時得到鎖。一樣能夠用來緩解死鎖問題
  • 支持公平鎖避免飢餓
  • 好基友Condition。雖然synchronized下一樣有Object的wait notify方法,但Condition隊列更靈活、更可控。
  • 使用CAS來實現原子操做
  • 使用LockSupport中的park unpark實現阻塞原語

1.1 基本使用

  一個老生常談的多線程自增的例子。因爲i++的非原子性,在不採起任何措施的狀況下最終的結果是小於等於200000的,爲了使最終的結果爲200000須要採起措施保障i++不可分割,在i++先後加鎖便可。安全

  雖然用synchronized可實現一樣的結果,但用重入鎖能夠本身來加鎖解鎖,何況還省了一個大括號不是?多線程

public class myThread implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    public static int i=0;

    @Override
    public void run() {
        for (int j=0;j<100000;j++){
            lock.lock();
            i++;
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        myThread thread = new myThread();
        Thread t1 = new Thread(thread);
        Thread t2 = new Thread(thread);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}

1.2 中斷與死鎖

  首先是一個死鎖的代碼,線程1先得到鎖1而後請求得到鎖2,線程2先得到鎖2而後請求得到鎖1。框架

package ReLock;

import java.util.concurrent.locks.ReentrantLock;

public class deadLock implements Runnable {
    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();
    int lock;

    public deadLock(int lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        if (lock == 1){
            try {
                lock1.lockInterruptibly();
                Thread.sleep(500);
                lock2.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            try {
                lock2.lockInterruptibly();
                Thread.sleep(500);
                lock1.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        deadLock deadlock1 = new deadLock(1);
        Thread thread1 = new Thread(deadlock1);
        deadLock deadlock2 = new deadLock(2);
        Thread thread2 = new Thread(deadlock2);
        thread1.start();
        thread2.start();
        
    }
}

  執行以後沒有反應且顯示正在運行。用jps找到當前死鎖進程的pidide

  而後用jstack -l pid查看進程的狀態,能夠看到jstack分析出了死鎖,且指出了死鎖的緣由。性能

  一樣的可使用jConsole圖形化界面的查看死鎖。優化

  總之咱們如今有了一個死鎖的代碼。注意到這裏獲取不是lock而是lockInterruptibly,意味着沒有得到鎖的線程能夠響應中斷。處於WAITING狀態的線程響應中斷方式是拋出異常,咱們catch到異常後就能夠在異常處理邏輯中釋放鎖。ui

  按照這個思路修改代碼,在catch中加入釋放鎖的邏輯。注意響應中斷並不會直接釋放鎖,要在catch邏輯裏手動釋放鎖。而且爲了代碼的健壯性,先判斷當前哪些鎖被當先線程持有,釋放當前線程持有的鎖。在主線程里加入線程中斷代碼,這樣在中斷一個線程的時候能夠看到另外一個線程完整的執行。this

package ReLock;

import java.util.concurrent.locks.ReentrantLock;

public class deadLock implements Runnable {
    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();
    int lock;

    public deadLock(int lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        if (lock == 1){
            try {
                lock1.lockInterruptibly();
                Thread.sleep(500);
                lock2.lockInterruptibly();
                System.out.println("1執行完畢");
            } catch (InterruptedException e) {
                if (lock1.isHeldByCurrentThread()){
                    lock1.unlock();
                }
                if (lock2.isHeldByCurrentThread()){
                    lock2.unlock();
                }
                e.printStackTrace();
            }
        }else {
            try {
                lock2.lockInterruptibly();
                Thread.sleep(500);
                lock1.lockInterruptibly();
                System.out.println("2執行完畢");
            } catch (InterruptedException e) {
                if (lock1.isHeldByCurrentThread()){
                    lock1.unlock();
                }
                if (lock2.isHeldByCurrentThread()){
                    lock2.unlock();
                }
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        deadLock deadlock1 = new deadLock(1);
        Thread thread1 = new Thread(deadlock1);
        deadLock deadlock2 = new deadLock(2);
        Thread thread2 = new Thread(deadlock2);
        thread1.start();
        thread2.start();
        Thread.sleep(100);
        thread2.interrupt();

    }
}

   但關於利用中斷來解決死鎖我又想到了新的思路:把lock.lockInterruptibly()換成lock.lock也是能夠的,但前提是Thread.sleep要加進去。因此推廣這個結論只要可以拋出InterruptedException異常的代碼均可以用中斷去打斷。單獨使用lock.lock不能夠,由於單獨使用lock.lock不會拋出中斷異常。spa

  好比下面這段代碼,死鎖的線程不會響應中斷。

public class lockInter {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                System.out.println("解鎖了");
            }
        });
        lock.lock();
        thread.start();
        thread.interrupt();
    }
    
}

 

 1.3 超時獲取鎖與死鎖

  這是避免死鎖的第二種思路,若是一個線程得到鎖失敗後等待超過必定時間就會返回,而非一直等待,又看到了樂觀鎖的影子。

  以下所示,總體結構而言和最初的同樣,這理應是一個死鎖,可是通過一些等待後最終有了正確的返回結果。

package ReLock;

import java.util.concurrent.locks.ReentrantLock;

public class tryAcquireKillDeadLock implements Runnable{
    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();
    private int num;

    public tryAcquireKillDeadLock(int num) {
        this.num = num;
    }

    @Override
    public void run() {
        if (num ==1){
            while (true){
                if (lock1.tryLock()){
                    try {
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        if (lock2.tryLock()){
                            try {
                                System.out.println(Thread.currentThread().getName()+" MyJob Done");
                                return;
                            } finally {
                                lock2.unlock();
                            }
                        }
                    } finally {
                        lock1.unlock();
                    }
                }
            }
        }else {
            while (true){
                if (lock2.tryLock()){
                    try {
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        if (lock1.tryLock()){
                            try {
                                System.out.println(Thread.currentThread().getName()+" MyJob Done");
                                return;
                            } finally {
                                lock1.unlock();
                            }
                        }
                    } finally {
                        lock2.unlock();
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        tryAcquireKillDeadLock r1 = new tryAcquireKillDeadLock(1);
        tryAcquireKillDeadLock r2 = new tryAcquireKillDeadLock(2);
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();
    }
}

   這裏使用的釋放鎖的方法比較優雅,在finally裏釋放鎖,雖然在finally前面有一個return語句,但finally裏的釋放鎖的代碼也會執行,而且按照從內向外的順序,在執行完畢後再執行return。

public class testTry {
    public static int testT(){
        try {
            try {
                int a =5;
                //int a = 5/0;
                return a;
            }finally {
                System.out.println("我執行了");
            }
        } finally {
            System.out.println("我也執行了");
        }
    }

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

   另外關注一下這裏鎖的使用與釋放的代碼模板,拋去sleep引入的try可使代碼簡潔一點。

  • 最外層要用while包裹起來,由於tryLock在未得到鎖會返回false。
  • 每次進入if都表明鎖獲取成功,把裏面的代碼用try finally包裹起來,這是爲了不在執行代碼的時候拋出異常致使鎖沒有釋放引起死鎖,在finally裏釋放鎖是最安全的行爲
  • 若是有屢次得到鎖的操做就在try裏嵌套try finally
      while (true){
                if (lock1.tryLock()){
                    try {
                        if (lock2.tryLock()){
                            try {
                                //TO DO
                                return;
                            } finally {
                                lock2.unlock();
                            }
                        }
                    } finally {
                        lock1.unlock();
                    }
                }
            }

1.4 公平鎖

  公平與否是藉助底層AQS實現的,書上說非公平的定義是每次釋放鎖的時候從等待隊列裏隨機取出一個等待的線程給予鎖,對次我存疑。

 

package ReLock;

import java.util.concurrent.locks.ReentrantLock;

public class FairReLock implements Runnable {
    private static ReentrantLock lock = new ReentrantLock(false);

    @Override
    public void run() {
        while (true){
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName()+" 得到鎖");
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        FairReLock fairReLock = new FairReLock();
        Thread t1 = new Thread(fairReLock);
        Thread t2 = new Thread(fairReLock);
        t1.start();
        t2.start();
    }
}

  當使用公平鎖的時候打印結果是左邊,能夠看到基本上兩個線程是交替得到鎖;使用非公平鎖的結果在右邊,明顯看到鎖是長期被一個線程霸佔後又給了另外一個線程。於是公平鎖最大的優勢是避免飢餓產生,雖然須要付出必定的代價。

相關文章
相關標籤/搜索