Java併發編程學習(8)---ReentrantLock學習

目錄頁:https://blog.csdn.net/u011294519/article/details/88367808

1. ReentrantLock

1.1.小聲嗶嗶

    年輕的時候,說到鎖想到的就是synchronized關鍵字,而且那時候項目要求不高,已經完全能滿足需求,但是深入瞭解synchronized以後發現自己真的是年輕(貌似說synchronized已經被優化的與Lock差不多,但是真的瞭解過後發現由於synchronized本身的限制導致在讀寫鎖方面是synchronized永遠也趕不上的)。

    ReentrantLock從命名上來看就是可重入,但是synchronized其實也是可重入的

ReentrantLock與synchronized的區別:

  1. synchronized是依賴於JVM實現的,而ReenTrantLock是JDK實現的。
  2. 如果一個方法被Synchronized修飾作爲對象鎖,甚至是類鎖(本質也是對象鎖),那麼一個線程獲取到鎖後其他線程就被阻塞,直到該線程釋放鎖。ReentrantLock支持設置等待時間,並且可阻斷。
  3. 在釋放鎖的方式上,synchronized在開發過程中完全可以不用去關心,在方法執行完成或執行異常後,JVM會將鎖釋放,這也是內置關鍵字的好處。而ReentrantLock則需要我們顯示的釋放鎖,而且必須是要寫在finally中防止異常導致死鎖。由此可以看出在使用方式上ReenTrantLock比synchronized更加靈活,但是synchronized使用起來更加簡單。

1.2. ReentrantLock的一些特性

  1. ReenTrantLock可以通過構造方法指定使用公平鎖還是非公平鎖。Synchronized關鍵字只能是非公平鎖。(PS:公平鎖就是FIFO這種,但是非公平鎖效率會更高,因爲公平鎖要加判斷等進行實現,有這功夫活都幹完了)
  2. ReenTrantLock利用AQS提供了一個Condition(條件)類,可以對線程分組喚醒,而synchronized則是會喚醒全部線程來競爭鎖。
  3. ReenTrantLock可以使用tryLock嘗試獲取鎖,並不是一直阻塞。

1.3.ReentrantLock主要方法

    構造方法ReentrantLock():初始化非公平鎖

    構造方法public ReentrantLock(boolean fair):傳入true初始化公平鎖,若傳入參數false則初始化非公平鎖。

    lock():獲取鎖

    unlock():釋放鎖

    boolean tryLock():嘗試獲取鎖,若獲取成功則返回true

    boolean tryLock (long timeout, TimeUnit unit):嘗試獲取鎖,若此時無法獲取鎖在等待timeout時間後放棄獲取鎖並返回false。

    lockInterruptibly():以響應中斷的方式加鎖

1.4. 上代碼

1.4.1. 實現與synchronized相同效果

    先使用ReentrantLock達到和synchronized相同的效果,使用遞歸體現可重入

package com.concurrent.aqslock.part8.reent;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 使用顯示鎖的範式
 */
public class LockDemo {
    private static ReentrantLock lock = new ReentrantLock();

    /**
     * 使用ReentrantLock鎖
     */
    static class ReenTrantThread implements Runnable {
        private int count = 0;

        @Override
        public void run() {
            cyclic();
        }

        private void cyclic() {
            lock.lock();
            try {
                while (count < 2) {
                    System.out.println(Thread.currentThread().getName() + " count value: " + count);
                    Thread.sleep(1000);
                    ++count;
                    System.out.printf("Lock is get by CurrentThread: %s %s \n", Thread.currentThread().getName(), lock.isHeldByCurrentThread());
                    cyclic();
                    Thread.sleep(3000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (count >= 2 && lock.isHeldByCurrentThread()) {
                    System.out.printf("%s unlock ,queue length: %s \n", Thread.currentThread().getName(), lock.getQueueLength());
                    lock.unlock();
                }
            }
        }
    }

    /**
     * 使用synchronized鎖
     */
    static class SysThread implements Runnable {
        private int count = 0;

        @Override
        public void run() {
            cyclic();
        }

        private synchronized void cyclic() {
            while (count < 2) {
                System.out.println(Thread.currentThread().getName() + " count value: " + count);
                ++count;
                cyclic();
            }
        }
    }

    public static void main(String[] arg0) throws InterruptedException {
        for (int i = 0; i < 2; ++i) {
            new Thread(new ReenTrantThread(), "ReenTrantThread " + i).start();
        }

        for (int i = 0; i < 2; ++i) {
            new Thread(new SysThread(), "SysThread " + i).start();
        }

        Thread.sleep(3000);
    }

}

運行結果:

代碼位置:aqs-lock的part8

1.4.2. 使用可中斷式獲取鎖

package com.concurrent.aqslock.part8.reent;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 演示可中斷式獲取鎖
 */
public class InterruptReenTrant {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] arg0) {

        Thread t1 = new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + " before get lock");
                lock.lockInterruptibly();
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName() + " after get lock");

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    System.out.println(Thread.currentThread().getName() + " unlock");
                    lock.unlock();
                }
            }
        }, "first thread");

        Thread t2 = new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + " before get lock");
                lock.lockInterruptibly();
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + " after get lock");

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    System.out.println(Thread.currentThread().getName() + " unlock");
                    lock.unlock();
                }
            }
        }, "second thread");

        t1.start();
        t2.start();
        // 嘗試註釋和不註釋下面代碼
        t1.interrupt();
    }

}

運行結果:

    代碼位置:aqs-lock的part8

1.4.3. tryLock

package com.concurrent.aqslock.part8.reent;

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

/**
 * 演示tryLock
 */
public class TryLockReenTrant {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] arg0) {
        Thread t1 = new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + " before try lock");
                if (lock.tryLock()) {
                    Thread.sleep(3000);
                    System.out.println(Thread.currentThread().getName() + " after try lock");
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    System.out.println(Thread.currentThread().getName() + " unlock");
                    lock.unlock();
                }
            }
        }, "first thread");

        Thread t2 = new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + " before try lock");
                if (lock.tryLock()) {
                    Thread.sleep(2000);
                    System.out.println(Thread.currentThread().getName() + " after try lock");
                } else {
                    System.out.println(Thread.currentThread().getName() + " try lock fail");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    System.out.println(Thread.currentThread().getName() + " unlock");
                    lock.unlock();
                }
            }
        }, "second thread");

        Thread t3 = new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + " before try lock");
                if (lock.tryLock(6L, TimeUnit.SECONDS)) {
                    Thread.sleep(2000);
                    System.out.println(Thread.currentThread().getName() + " after try lock");
                } else {
                    System.out.println(Thread.currentThread().getName() + " try lock fail");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    System.out.println(Thread.currentThread().getName() + " unlock");
                    lock.unlock();
                }
            }
        }, "third thread");

        t1.start();
        t2.start();
        t3.start();
    }
}

運行結果:

    代碼位置:aqs-lock的part8

1.5. 看代碼

    非公平隊列的Lock():

    可以看到,使用了我們的老朋友AQS的compareAndSetState方法,若state爲0時則置爲1,設置鎖的擁有者爲當前線程。

    若更新state失敗,則進入acquire方法

    若獲取到鎖的爲當前現在,則給state加1,並直接返回

    若獲取到鎖的不是當前線程,則進入acquireQueued方法

    首先將當前線程加入等待隊列,並掛起當前線程。

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