聊一聊鎖

1、公平鎖、非公平鎖

  1. 是什么

    公平鎖就是先來后到、非公平鎖就是允許加塞,Lock lock = new ReentrantLock(Boolean fair); 默認(rèn)非公平。

    • ==公平鎖==是指多個(gè)線程按照申請鎖的順序來獲取鎖,類似排隊(duì)打飯。

    • ==非公平鎖==是指多個(gè)線程獲取鎖的順序并不是按照申請鎖的順序,有可能后申請的線程優(yōu)先獲取鎖,在高并發(fā)的情況下,有可能會(huì)造成優(yōu)先級(jí)反轉(zhuǎn)或者節(jié)現(xiàn)象。

  2. 兩者區(qū)別

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

      公平鎖,就是很公平,在并發(fā)環(huán)境中,每個(gè)線程在獲取鎖時(shí),會(huì)先查看此鎖維護(hù)的等待隊(duì)列,如果為空,或者當(dāng)前線程就是等待隊(duì)列的第一個(gè),就占有鎖,否則就會(huì)加入到等待隊(duì)列中,以后會(huì)按照FIFO的規(guī)則從隊(duì)列中取到自己。

    • 非公平鎖: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.

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

2、可重入鎖(遞歸鎖)

  1. 遞歸鎖是什么

    指的時(shí)同一線程外層函數(shù)獲得鎖之后,內(nèi)層遞歸函數(shù)仍然能獲取該鎖的代碼,在同一個(gè)線程在外層方法獲取鎖的時(shí)候,在進(jìn)入內(nèi)層方法會(huì)自動(dòng)獲取鎖,也就是說,線程可以進(jìn)入任何一個(gè)它已經(jīng)擁有的鎖所同步著的代碼塊

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

  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、獨(dú)占鎖(寫鎖)/共享鎖(讀鎖)/互斥鎖

  1. 概念

    • 獨(dú)占鎖:指該鎖一次只能被一個(gè)線程所持有,對ReentrantLock和Synchronized而言都是獨(dú)占鎖

    • 共享鎖:只該鎖可被多個(gè)線程所持有

      ReentrantReadWriteLock其讀鎖是共享鎖,寫鎖是獨(dú)占鎖

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

  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;
    
    /**
     * 多個(gè)線程同時(shí)讀一個(gè)資源類沒有任何問題,所以為了滿足并發(fā)量,讀取共享資源應(yīng)該可以同時(shí)進(jìn)行。
     * 但是
     * 如果有一個(gè)線程象取寫共享資源來,就不應(yīng)該自由其他線程可以對資源進(jìn)行讀或?qū)? * 總結(jié)
     * 讀讀能共存
     * 讀寫不能共存
     * 寫寫不能共存
     */
    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();
    
        /**
         * 寫操作:原子+獨(dú)占
         * 整個(gè)過程必須是一個(gè)完整的統(tǒng)一體,中間不許被分割,不許被打斷
         *
         * @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

    是指嘗試獲取鎖的線程不會(huì)立即阻塞,而是采用循環(huán)的方式去嘗試獲取鎖,這樣的好處是減少線程上下文切換的消耗,缺點(diǎn)是循環(huán)會(huì)消耗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;
    
    /**
     * 實(shí)現(xiàn)自旋鎖
     * 自旋鎖好處,循環(huán)比較獲取知道成功位置,沒有類似wait的阻塞
     *
     * 通過CAS操作完成自旋鎖,A線程先進(jìn)來調(diào)用mylock方法自己持有鎖5秒鐘,B隨后進(jìn)來發(fā)現(xiàn)當(dāng)前有線程持有鎖,不是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()");
        }
    }
    

鎖升級(jí)

偏向鎖--》輕量級(jí)鎖 (自旋鎖) --》重量級(jí)鎖

鎖優(yōu)化

鎖粗化

鎖消除
適應(yīng)性自旋:適應(yīng)性自旋,線程如果自旋成功了,則下次自旋的次數(shù)會(huì)更多,如果自旋失敗了,則自旋的次數(shù)就會(huì)減少。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • JUC 原創(chuàng)者:文思,感謝尚硅谷,資料來源于尚硅谷 目錄: 1、volatile關(guān)鍵字與內(nèi)存可見性 2、原子變量與...
    文思li閱讀 2,520評(píng)論 0 1
  • ZooKeeper是一個(gè)分布式的,開放源碼的分布式應(yīng)用程序協(xié)調(diào)服務(wù),是Google的Chubby一個(gè)開源的實(shí)現(xiàn),是...
    Java架構(gòu)007閱讀 2,386評(píng)論 0 4
  • 本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等。 首先講...
    李欣陽閱讀 2,591評(píng)論 1 15
  • Java多線程學(xué)習(xí) [-] 一擴(kuò)展javalangThread類 二實(shí)現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 3,105評(píng)論 1 18
  • 一、wait--notify--sleep Object obj = new Object(); obj.wait...
    fe0180bd6eaf閱讀 391評(píng)論 0 1

友情鏈接更多精彩內(nèi)容