四:java中的鎖分類

java 中鎖有哪些

1.公平和非公平鎖

  • 是什麼
    • **公平鎖:**是指多個線程按照申請的順序來獲取值
    • **非公平鎖:**是值多個線程獲取值的順序並不是按照申請鎖的順序,有可能後申請的線程比先申請的線程優先獲取鎖,在高併發的情況下,可能會造成優先級翻轉或者飢餓現象
  • 兩者區別
    • **公平鎖:**在併發環境中,每一個線程在獲取鎖時會先查看此鎖維護的等待隊列,如果爲空,或者當前線程是等待隊列的第一個就佔有鎖,否者就會加入到等待隊列中,以後會按照 FIFO 的規則獲取鎖
    • **非公平鎖:**一上來就嘗試佔有鎖,如果失敗在進行排隊

2.可重入鎖和不可重入鎖

  • 是什麼

    • **可重入鎖:**指的是同一個線程外層函數獲得鎖之後,內層仍然能獲取到該鎖,在同一個線程在外層方法獲取鎖的時候,在進入內層方法或會自動獲取該鎖
    • 不可重入鎖: 所謂不可重入鎖,即若當前線程執行某個方法已經獲取了該鎖,那麼在方法中嘗試再次獲取鎖時,就會獲取不到被阻塞
  • 代碼實現

    • 可重入鎖

      public class ReentrantLock {
          boolean isLocked = false;
          Thread lockedBy = null;
          int lockedCount = 0;
          public synchronized void lock() throws InterruptedException {
              Thread thread = Thread.currentThread();
              while (isLocked && lockedBy != thread) {
                  wait();
              }
              isLocked = true;
              lockedCount++;
              lockedBy = thread;
          }
          
          public synchronized void unlock() {
              if (Thread.currentThread() == lockedBy) {
                  lockedCount--;
                  if (lockedCount == 0) {
                      isLocked = false;
                      notify();
                  }
              }
          }
      }
      

      測試

      public class Count {
      //    NotReentrantLock lock = new NotReentrantLock();
          ReentrantLock lock = new ReentrantLock();
          public void print() throws InterruptedException{
              lock.lock();
              doAdd();
              lock.unlock();
          }
      
          private void doAdd() throws InterruptedException {
              lock.lock();
              // do something
              System.out.println("ReentrantLock");
              lock.unlock();
          }
      
          public static void main(String[] args) throws InterruptedException {
              Count count = new Count();
              count.print();
          }
      }
      

      發現可以輸出 ReentrantLock,我們設計兩個線程調用 print() 方法,第一個線程調用 print() 方法獲取鎖,進入 lock() 方法,由於初始 lockedBy 是 null,所以不會進入 while 而掛起當前線程,而是是增量 lockedCount 並記錄 lockBy 爲第一個線程。接着第一個線程進入 doAdd() 方法,由於同一進程,所以不會進入 while 而掛起,接着增量 lockedCount,當第二個線程嘗試lock,由於 isLocked=true,所以他不會獲取該鎖,直到第一個線程調用兩次 unlock() 將 lockCount 遞減爲0,纔將標記爲 isLocked 設置爲 false。

    • 不可重入鎖

      public class NotReentrantLock {
          private boolean isLocked = false;
          public synchronized void lock() throws InterruptedException {
              while (isLocked) {
                  wait();
              }
              isLocked = true;
          }
          public synchronized void unlock() {
              isLocked = false;
              notify();
          }
      }
      

      測試

      public class Count {
          NotReentrantLock lock = new NotReentrantLock();
          public void print() throws InterruptedException{
              lock.lock();
              doAdd();
              lock.unlock();
          }
      
          private void doAdd() throws InterruptedException {
              lock.lock();
              // do something
              lock.unlock();
          }
      
          public static void main(String[] args) throws InterruptedException {
              Count count = new Count();
              count.print();
          }
      }
      

      當前線程執行print()方法首先獲取lock,接下來執行doAdd()方法就無法執行doAdd()中的邏輯,必須先釋放鎖。這個例子很好的說明了不可重入鎖。

  • synchronized 和 ReentrantLock 都是可重入鎖

    • synchronzied

      public class SynchronziedDemo {
      
          private synchronized void print() {
              doAdd();
          }
          private synchronized void doAdd() {
              System.out.println("doAdd...");
          }
      
          public static void main(String[] args) {
              SynchronziedDemo synchronziedDemo = new SynchronziedDemo();
              synchronziedDemo.print(); // doAdd...
          }
      }
      

      上面可以說明 synchronized 是可重入鎖。

    • ReentrantLock

      public class ReentrantLockDemo {
          private Lock lock = new ReentrantLock();
      
          private void print() {
              lock.lock();
              doAdd();
              lock.unlock();
          }
      
          private void doAdd() {
              lock.lock();
              lock.lock();
              System.out.println("doAdd...");
              lock.unlock();
              lock.unlock();
          }
      
          public static void main(String[] args) {
              ReentrantLockDemo reentrantLockDemo = new ReentrantLockDemo();
              reentrantLockDemo.print();
          }
      }
      

      上面例子可以說明 ReentrantLock 是可重入鎖,而且在 #doAdd 方法中加兩次鎖和解兩次鎖也可以。

3.自旋鎖

  • 是指定嘗試獲取鎖的線程不會立即堵塞,而是採用循環的方式去嘗試獲取鎖,這樣的好處是減少線程上線文切換的消耗,缺點就是循環會消耗 CPU。
  • 手動實現自旋鎖
public class SpinLock {
    private AtomicReference<Thread> atomicReference = new AtomicReference<>();
    private void lock () {
        System.out.println(Thread.currentThread() + " coming...");
        while (!atomicReference.compareAndSet(null, Thread.currentThread())) {
            // loop
        }
    }

    private void unlock() {
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread, null);
        System.out.println(thread + " unlock...");
    }

    public static void main(String[] args) throws InterruptedException {
        SpinLock spinLock = new SpinLock();
        new Thread(() -> {
            spinLock.lock();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("hahaha");
            spinLock.unlock();

        }).start();

        Thread.sleep(1);

        new Thread(() -> {
            spinLock.lock();
            System.out.println("hehehe");
            spinLock.unlock();
        }).start();
    }
}
輸出:

Thread[Thread-0,5,main] coming...
Thread[Thread-1,5,main] coming...
hahaha
Thread[Thread-0,5,main] unlock...
hehehe
Thread[Thread-1,5,main] unlock...

4.獨佔鎖(寫鎖)/共享鎖(讀鎖)

  • 是什麼

    • **獨佔鎖:**指該鎖一次只能被一個線程持有
    • **共享鎖:**該鎖可以被多個線程持有
  • 對於 ReentrantLock 和 synchronized 都是獨佔鎖;對與 ReentrantReadWriteLock 其讀鎖是共享鎖而寫鎖是獨佔鎖。讀鎖的共享可保證併發讀是非常高效的,讀寫、寫讀和寫寫的過程是互斥的。

  • 讀寫鎖例子

  • public class MyCache {
       
        private volatile Map<String, Object> map = new HashMap<>();
    
        private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        WriteLock writeLock = lock.writeLock();
        ReadLock readLock = lock.readLock();
    
        public void put(String key, Object value) {
            try {
                writeLock.lock();
                System.out.println(Thread.currentThread().getName() + " 正在寫入...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                map.put(key, value);
                System.out.println(Thread.currentThread().getName() + " 寫入完成,寫入結果是 " + value);
            } finally {
                writeLock.unlock();
            }
        }
    
        public void get(String key) {
            try {
                readLock.lock();
                System.out.println(Thread.currentThread().getName() + " 正在讀...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Object res = map.get(key);
                System.out.println(Thread.currentThread().getName() + " 讀取完成,讀取結果是 " + res);
            } finally {
                readLock.unlock();
            }
        }
    }
    
    
    

    測試

    
    public class ReadWriteLockDemo {
        public static void main(String[] args) {
            MyCache cache = new MyCache();
    
            for (int i = 0; i < 5; i++) {
                final int temp = i;
                new Thread(() -> {
                    cache.put(temp + "", temp + "");
                }).start();
            }
    
            for (int i = 0; i < 5; i++) {
                final int temp = i;
                new Thread(() -> {
                    cache.get(temp + "");
                }).start();
            }
        }
    }
    輸出結果
    
    Thread-0 正在寫入...
    Thread-0 寫入完成,寫入結果是 0
    Thread-1 正在寫入...
    Thread-1 寫入完成,寫入結果是 1
    Thread-2 正在寫入...
    Thread-2 寫入完成,寫入結果是 2
    Thread-3 正在寫入...
    Thread-3 寫入完成,寫入結果是 3
    Thread-4 正在寫入...
    Thread-4 寫入完成,寫入結果是 4
    Thread-5 正在讀...
    Thread-7 正在讀...
    Thread-8 正在讀...
    Thread-6 正在讀...
    Thread-9 正在讀...
    Thread-5 讀取完成,讀取結果是 0
    Thread-7 讀取完成,讀取結果是 2
    Thread-8 讀取完成,讀取結果是 3
    Thread-6 讀取完成,讀取結果是 1
    Thread-9 讀取完成,讀取結果是 4
    

能保證讀寫寫讀寫寫的過程是互斥的時候是獨享的,讀讀的時候是共享的。

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