1. 程式人生 > 實用技巧 >公平鎖、非公平鎖、可重入鎖、遞迴鎖、自旋鎖?手寫自旋鎖

公平鎖、非公平鎖、可重入鎖、遞迴鎖、自旋鎖?手寫自旋鎖

1、公平鎖、非公平鎖

  1. 是什麼

    公平鎖就是先來後到、非公平鎖就是允許加塞,Lock lock = new ReentrantLock(Boolean fair);預設非公平。

    • **==公平鎖==**是指多個執行緒按照申請鎖的順序來獲取鎖,類似排隊打飯。

    • **==非公平鎖==**是指多個執行緒獲取鎖的順序並不是按照申請鎖的順序,有可能後申請的執行緒優先獲取鎖,在高併發的情況下,有可能會造成優先順序反轉或者節現象。

  2. 兩者區別

    • 公平鎖:Threads acquire a fair lock in the order in which they requested it

      公平鎖,就是很公平,在併發環境中,每個執行緒在獲取鎖時,會先檢視此鎖維護的等待佇列,如果為空,或者當前執行緒就是等待佇列的第一個,就佔有鎖,否則就會加入到等待佇列中,以後會按照FIFO的規則從佇列中取到自己。

    • 非公平鎖:a nonfair lock permits barging: threads requesting a lock can jump ahead of the queue of waiting threads if the lock happens to be available when it is requested.

      非公平鎖比較粗魯,上來就直接嘗試佔有額,如果嘗試失敗,就再採用類似公平鎖那種方式。

  3. other

    對Java ReentrantLock而言,通過建構函式指定該鎖是否公平,磨粉是非公平鎖,非公平鎖的優點在於吞吐量比公平鎖大

    對Synchronized而言,是一種非公平鎖

2、可重入所(遞迴鎖)

  1. 遞迴鎖是什麼

    指的時同一執行緒外層函式獲得鎖之後,內層遞迴函式仍然能獲取該鎖的程式碼,在同一個執行緒在外層方法獲取鎖的時候,在進入內層方法會自動獲取鎖,也就是說,==執行緒可以進入任何一個它已經擁有的鎖所同步著的程式碼塊==

  2. ReentrantLock/Synchronized 就是一個典型的可重入鎖

  3. 可重入鎖最大的作用是避免死鎖

  4. 程式碼示例

    package com.jian8.juc.lock;
    
    ####
        public static void main(String[] args) {
            Phone phone = new Phone();
            new Thread(() -> {
                try {
                    phone.sendSMS();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "Thread 1").start();
            new Thread(() -> {
                try {
                    phone.sendSMS();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "Thread 2").start();
        }
    }
    class Phone{
        public synchronized void sendSMS()throws Exception{
            System.out.println(Thread.currentThread().getName()+"\t -----invoked sendSMS()");
            Thread.sleep(3000);
            sendEmail();
        }
    
        public synchronized void sendEmail() throws Exception{
            System.out.println(Thread.currentThread().getName()+"\t +++++invoked sendEmail()");
        }
    }
    package com.jian8.juc.lock;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ReentrantLockDemo {
        public static void main(String[] args) {
            Mobile mobile = new Mobile();
            new Thread(mobile).start();
            new Thread(mobile).start();
        }
    }
    class Mobile implements Runnable{
        Lock lock = new ReentrantLock();
        @Override
        public void run() {
            get();
        }
    
        public void get() {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"\t invoked get()");
                set();
            }finally {
                lock.unlock();
            }
        }
        public void set(){
            lock.lock();
            try{
                System.out.println(Thread.currentThread().getName()+"\t invoked set()");
            }finally {
                lock.unlock();
            }
        }
    }
    

3、獨佔鎖(寫鎖)/共享鎖(讀鎖)/互斥鎖

  1. 概念

    • 獨佔鎖:指該鎖一次只能被一個執行緒所持有,對ReentrantLock和Synchronized而言都是獨佔鎖

    • 共享鎖:只該鎖可被多個執行緒所持有

      ReentrantReadWriteLock其讀鎖是共享鎖,寫鎖是獨佔鎖

    • 互斥鎖:讀鎖的共享鎖可以保證併發讀是非常高效的,讀寫、寫讀、寫寫的過程是互斥的

  2. 程式碼示例

    package com.jian8.juc.lock;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    /**
     * 多個執行緒同時讀一個資源類沒有任何問題,所以為了滿足併發量,讀取共享資源應該可以同時進行。
     * 但是
     * 如果有一個執行緒象取寫共享資源來,就不應該自由其他執行緒可以對資源進行讀或寫
     * 總結
     * 讀讀能共存
     * 讀寫不能共存
     * 寫寫不能共存
     */
    public class ReadWriteLockDemo {
        public static void main(String[] args) {
            MyCache myCache = new MyCache();
            for (int i = 1; i <= 5; i++) {
                final int tempInt = i;
                new Thread(() -> {
                    myCache.put(tempInt + "", tempInt + "");
                }, "Thread " + i).start();
            }
            for (int i = 1; i <= 5; i++) {
                final int tempInt = i;
                new Thread(() -> {
                    myCache.get(tempInt + "");
                }, "Thread " + i).start();
            }
        }
    }
    
    class MyCache {
        private volatile Map<String, Object> map = new HashMap<>();
        private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    
        /**
         * 寫操作:原子+獨佔
         * 整個過程必須是一個完整的統一體,中間不許被分割,不許被打斷
         *
         * @param key
         * @param value
         */
        public void put(String key, Object value) {
            rwLock.writeLock().lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t正在寫入:" + key);
                TimeUnit.MILLISECONDS.sleep(300);
                map.put(key, value);
                System.out.println(Thread.currentThread().getName() + "\t寫入完成");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                rwLock.writeLock().unlock();
            }
    
        }
    
        public void get(String key) {
            rwLock.readLock().lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t正在讀取:" + key);
                TimeUnit.MILLISECONDS.sleep(300);
                Object result = map.get(key);
                System.out.println(Thread.currentThread().getName() + "\t讀取完成: " + result);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                rwLock.readLock().unlock();
            }
    
        }
    
        public void clear() {
            map.clear();
        }
    }

4、自旋鎖

  1. spinlock

    是指嘗試獲取鎖的執行緒不會立即阻塞,而是==採用迴圈的方式去嘗試獲取鎖==,這樣的好處是減少執行緒上下文切換的消耗,缺點是迴圈會消耗CPU

        public final int getAndAddInt(Object var1, long var2, int var4) {
            int var5;
            do {
                var5 = this.getIntVolatile(var1, var2);
            } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
            return var5;
        }

    手寫自旋鎖:

    package com.jian8.juc.lock;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicReference;
    
    /**
     * 實現自旋鎖
     * 自旋鎖好處,迴圈比較獲取知道成功位置,沒有類似wait的阻塞
     *
     * 通過CAS操作完成自旋鎖,A執行緒先進來呼叫mylock方法自己持有鎖5秒鐘,B隨後進來發現當前有執行緒持有鎖,不是null,所以只能通過自旋等待,知道A釋放鎖後B隨後搶到
     */
    public class SpinLockDemo {
        public static void main(String[] args) {
            SpinLockDemo spinLockDemo = new SpinLockDemo();
            new Thread(() -> {
                spinLockDemo.mylock();
                try {
                    TimeUnit.SECONDS.sleep(3);
                }catch (Exception e){
                    e.printStackTrace();
                }
                spinLockDemo.myUnlock();
            }, "Thread 1").start();
    
            try {
                TimeUnit.SECONDS.sleep(3);
            }catch (Exception e){
                e.printStackTrace();
            }
    
            new Thread(() -> {
                spinLockDemo.mylock();
                spinLockDemo.myUnlock();
            }, "Thread 2").start();
        }
    
        //原子引用執行緒
        AtomicReference<Thread> atomicReference = new AtomicReference<>();
    
        public void mylock() {
            Thread thread = Thread.currentThread();
            System.out.println(Thread.currentThread().getName() + "\t come in");
            while (!atomicReference.compareAndSet(null, thread)) {
    
            }
        }
    
        public void myUnlock() {
            Thread thread = Thread.currentThread();
            atomicReference.compareAndSet(thread, null);
            System.out.println(Thread.currentThread().getName()+"\t invoked myunlock()");
        }
    }