java多線程之Lock介紹

java.util.concurrent包中有關於Lock鎖的定義。它提供了ReentrantLock、ReetrantReadWriteLock.ReadLock 和 ReetrantReadWriteLock.WriteLock,重入鎖、讀鎖和寫鎖等。

ReentrantLock

公平鎖和非公平鎖

公平鎖當線程請求鎖時,會將其加入到請求隊列中,
非公平鎖就是當前線程不管有無請求隊列,先去請求鎖,如果請求不到,加入到隊列末尾。
具體分析可以參考
http://www.jianshu.com/p/f7d05d06ef54

默認構造方法創建非公平鎖

public ReentrantLock() {
        sync = new NonfairSync();
    }

Lock與synchronized的區別

  1. lock是顯示的鎖,調用lock()添加鎖,必須調用unlock()釋放鎖。
  2. 使用lock.lock()調用線程的interrupt會中斷線程,而synchronized鎖調用interrupt不會中斷線程。

實例來證明2的結論

我們知道synchronized的interrupt方法只是設置中斷標誌,並沒有真正的中斷線程。下面這個代碼示例用於說明,
代碼如下:

設計兩個線程,一個read線程,一個write線程。
當read線程正在read的時候,write線程處於等待狀態、
然後中斷write線程,看會有什麼情況發生
如果write中斷,會打印 write end…
因爲Reentrant對中斷鎖做出響應,會中斷線程的執行。

public static void syncReaderWriterTest(){
        MyReentrantLockBuffer mySyncBuffer = new MyReentrantLockBuffer();
        final Thread reader = new Thread(new SyncReader(mySyncBuffer));
        final Thread writer = new Thread(new SyncWriter(mySyncBuffer));
        reader.start();
        writer.start();

        System.out.println(Thread.currentThread().getName() +  " writer 不在等待了...");
        writer.interrupt();
        System.out.println(Thread.currentThread().getName() +  " writer 已經中斷, " + writer.isInterrupted());
    }

    /**
     * 寫操作,傳入MySyncBuffer的構造方法,保證兩個對象持有相同的對象鎖
     */
    static class SyncWriter implements Runnable{

        private MyReentrantLockBuffer mySyncBuffer;

        public SyncWriter(MyReentrantLockBuffer mySyncBuffer){
            this.mySyncBuffer = mySyncBuffer;
        }
        public void run() {
            try {
                mySyncBuffer.write();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " write end." + Thread.currentThread().isInterrupted());
        }
    }

    /**
     * 讀操作,傳入MySyncBuffer的構造方法,保證兩個對象持有相同的對象鎖
     */
    static class SyncReader implements Runnable{

        private MyReentrantLockBuffer mySyncBuffer;

        public SyncReader(MyReentrantLockBuffer mySyncBuffer){
            this.mySyncBuffer = mySyncBuffer;
        }
        public void run() {

            mySyncBuffer.read();

        }
    }

可重入性

鎖的可重入性體現在兩個方面
1. methodA和methodB擁有同一個對象鎖,線程進入A方法中,在沒有釋放鎖的前提下,可以直接調用methodB方法。

static class ReentrantDemoRunnable implements Runnable{
        final ReentrantLock reentrantLock = new ReentrantLock();
public void methodA() {
            System.out.println(" lock before methodAAAAAA ========" );
            reentrantLock.lock();

            try {
                System.out.println(" run methodA and lock..." );
                this.methodB();
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                System.out.println(" run methodA end unlock..." );
                reentrantLock.unlock();
            }
            System.out.println(" methodAAAAAA ======== end =====" );
        }

        public void methodB() {
            System.out.println(" lock before methodBBBBBB ========" );
            reentrantLock.lock();

            try {
                System.out.println(" run methodB and lock..." );

            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                System.out.println(" run methodB end unlock..." );
                reentrantLock.unlock();
            }
            System.out.println(" methodBBBBBB ======== end =====" );
        }
  }

多線程調用methodA執行的順序爲:

 lock before methodAAAAAA ========
 run methodA and lock...
 lock before methodBBBBBB ========
 run methodB and lock...
 run methodB end unlock...
 methodBBBBBB ======== end =====
 run methodA end unlock...
 methodAAAAAA ======== end =====
  1. 在加鎖的前提下,在子類方法中調用父類的方法。
static class Father{
        final ReentrantLock lock = new ReentrantLock();
        public void method(){
            lock.lock();
            try {
                System.out.println("father do something ");
            } finally {
                lock.unlock();
            }
        }
    }

    static class SubClass extends Father{

        @Override
        public void method(){
            lock.lock();
            try {
                System.out.println("subClass do something ");
                super.method();
            } finally {
                lock.unlock();
            }
        }
    }

public static void reentrantTest2(){
        SubClass subClass = new SubClass();
        subClass.method();
    }

打印結果:

subClass do something 
father do something 

ReentrantReadWriteLock

讀寫鎖,它有兩個內部類,分別是ReadLock和WriteLock
這兩個鎖分別有以下特性

多個讀鎖不互斥(證明)

一個方法內使用readLock.lock鎖,當多個線程訪問時候,如果線程A持有鎖,線程B也可以持有該鎖。兩個線程無需互相等待。

 static class ReadRunnable implements Runnable{

        final ReentrantReadWriteLock.ReadLock readLock = new ReentrantReadWriteLock().readLock();

        public void run() {
            readLock.lock();
            System.out.println(Thread.currentThread().getName() + " read start...");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                System.out.println(Thread.currentThread().getName() + " read end...");
                readLock.unlock();
            }
        }
    }
public static void readLockNotMutex(){
        ReadRunnable readRunnable = new ReadRunnable();
        //創建3個多線程,來說明讀鎖不互斥
        for (int i = 0; i < 3; i++) {
            Thread thread = new Thread(readRunnable);
            thread.start();
        }
    }

例子說明:創建多個線程,每個線程執行MyRunnable的run方法,run方法中使用讀鎖設置同步。發現會有多個run方法中打印亂序。
打印結果

     Thread-0 read start...
     Thread-1 read start...
     Thread-2 read start...
     Thread-0 read end...
     Thread-1 read end...
     Thread-2 read end...

說明讀鎖不互斥

多個寫鎖互斥(證明)

原理與讀鎖相反

static class WriteRunnable implements Runnable{

        final ReentrantReadWriteLock.WriteLock writeLock = new ReentrantReadWriteLock().writeLock();

        public void run() {
            writeLock.lock();
            System.out.println(Thread.currentThread().getName() + " write start...");
            try {
                Thread.sleep(2000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                System.out.println(Thread.currentThread().getName() + " write end...");
                writeLock.unlock();
            }
        }
    }
public static void writeLockMutex(){
        WriteRunnable writeRunnable = new WriteRunnable();
        //創建3個多線程,來說明讀鎖不互斥
        for (int i = 0; i < 3; i++) {
            Thread thread = new Thread(writeRunnable);
            thread.start();
        }
    }

創建多個線程,每個線程執行MyRunnable的run方法,run方法中使用寫鎖設置同步。發現會有多個run方法中打印不亂序。
打印結果:

     Thread-0 write start...
     Thread-0 write end...
     Thread-1 write start...
     Thread-1 write end...
     Thread-2 write start...
     Thread-2 write end...

說明寫鎖互斥

讀鎖和寫鎖互斥

  • 線程A執行writeMethod()方法,獲取到寫鎖,
  • 此時線程B執行readMethod()方法,因爲讀寫鎖(寫讀鎖)互斥,所以要等待線程A釋放鎖,然後才能繼續執行。
    實例如下:
static class ReadAndWriteLock{
        final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
        final ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
        final ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();

        public void writeMethod(){
            writeLock.lock();
            System.out.println(Thread.currentThread().getName() + " 當前爲write Lock========");
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                System.out.println(Thread.currentThread().getName() + " write lock end ========");
                writeLock.unlock();
            }
            System.out.println(Thread.currentThread().getName() + " 程序結束 end");
        }

        public void readMethod(){
            readLock.lock();
            System.out.println(Thread.currentThread().getName() + " 當前爲read Lock========");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                System.out.println(Thread.currentThread().getName() + " read lock end ========");
                readLock.unlock();
            }
            System.out.println(Thread.currentThread().getName() + " 程序結束 end");
        }
    }
public static void readWriteMutex(){
        ReadAndWriteLock readAndWriteLock = new ReadAndWriteLock();

        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                readAndWriteLock.writeMethod();
            }
        });
        t.start();


        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                readAndWriteLock.readMethod();
            }
        });
        t1.start();
    }

打印結果
讀鎖與寫鎖內容分別順序打印。說明讀寫鎖互斥。

     Thread-0 當前爲write Lock========
     Thread-0 write lock end ========
     Thread-0 程序結束 end
     Thread-1 當前爲read Lock========
     Thread-1 read lock end ========
     Thread-1 程序結束 end

讀鎖不支持Condition操作,而寫鎖支持

從源碼中看到,ReadLock的newCondition方法被禁用了

 public Condition newCondition() {
            throw new UnsupportedOperationException();
        }

而WriteLock可以創建

public Condition newCondition() {
            return sync.newCondition();
        }

支持降級鎖,不支持升級鎖

降級鎖: 線程進入writeLock,沒有釋放鎖,同時獲取到readLock。這時,writeLock會降級爲readLock。但是writeLock的鎖仍需手動釋放。ReentrantReadWriteLock支持降級鎖。

public void downGradeLock(){
            writeLock.lock();

            try {
                System.out.println(Thread.currentThread().getName() + " 當前進入寫鎖 write...");
                readLock.lock();
                System.out.println(Thread.currentThread().getName() + " 當前進入讀鎖 read...");
            } finally {
                System.out.println(Thread.currentThread().getName() + " 釋放讀鎖...");
                readLock.unlock();
                System.out.println(Thread.currentThread().getName() + " 釋放寫鎖...");
                writeLock.unlock();
            }
            System.out.println(Thread.currentThread().getName() + " 程序結束...");
        }

打印結果:
程序在持有寫鎖的同時,可以獲得讀鎖。說明,ReentrantReadWriteLock支持降級鎖。

main 當前進入寫鎖 write...
main 當前進入讀鎖 read...
main 釋放讀鎖...
main 釋放寫鎖...
main 程序結束...

升級鎖: 線程進入readLock,沒有釋放鎖,同時要獲取writeLock。
這時,程序進入死鎖狀態。ReentrantReadWriteLock不支持升級鎖。
調用下面的方法,會出現死鎖現象。

public void upGradeLock(){
            readLock.lock();
            System.out.println(Thread.currentThread().getName() + " 當前進入寫鎖 read...");
            writeLock.lock();
            System.out.println(Thread.currentThread().getName() + " 當前進入寫鎖 write...");
            writeLock.unlock();
            System.out.println("程序結束....");
        }

打印結果:

main 當前進入寫鎖 read...

並且程序一直在等待,嘗試獲取writeLock。

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