對公平鎖、非公平鎖、可重入鎖、遞歸鎖、自旋鎖的理解


 本篇文章主要是記錄自己的學習筆記,主要內容是:公平鎖、非公平鎖、可重入鎖、遞歸鎖、自旋鎖的理解,並實現一個自旋鎖。

公平和非公平鎖

(1)公平鎖和非公平鎖是什么?

公平鎖:是指多個線程按照申請鎖的順序來獲取鎖,類似排隊打飯,先來后到。

非公平鎖:是指多個線程獲取鎖的順序並不是按照申請鎖的循序,有可能后申請的線程比先申請的線程優先獲取鎖。但是,在高並發的情況下,有可能會造成優先級反轉或者飢餓現象。

(2)公平鎖和非公平鎖的區別是什么?

並發包中ReentrantLock的創建可以指定構造函數的boolean類型來得到公平鎖。

公平鎖和非公平鎖兩者的區別:

  公平鎖:線程按照他們申請鎖的順序獲取鎖,公平鎖就是很公平,在並發環境下,每個線程在獲取鎖時會先查看此鎖維護的等待隊列,如果隊列為空,獲取當前線程時等待隊列的第一個,就占有鎖。否則,就會加入到等待隊列中,以后會按照FIFO的規則從隊列中獲取鎖。

  非公平鎖:非公平所比較粗魯,上來就嘗試占有鎖,如果嘗試失敗,就采用類似公平鎖的方式獲取鎖。

補充:ReentrantLock即使通過構造函數指定該鎖是否是公平鎖,默認是非公平鎖。非公平鎖的優點就是吞吐量比公平鎖大。同樣的synchronized也是一種非公平鎖。

可重入鎖(又名遞歸鎖)

可重入鎖定義:指的是同一線程外層函數獲得鎖忠厚,內層遞歸函數仍能獲取該鎖。在同一個線程在外層黨法獲取鎖的時候,在進入內層方法會自動獲取鎖。也就是說。線程可以進入任何一個它已經擁有的鎖所同步着的代碼塊。

ReetrantLock和synchronized就是一個典型的可重入鎖,其最大的作用就是避免死鎖

下面我們用代碼來進行驗證:

class Phone{
    public synchronized void sendMessage(){
        System.out.println(Thread.currentThread().getId() + " \t " + " invoked sendMessage");
        sendEmail();
    }

    public synchronized void sendEmail(){
        System.out.println(Thread.currentThread().getId() + " \t " + " ###### invoked sendEmail");
        System.out.println("----------------------------------- 分割線  -----------------------------------");
        System.out.println();
    }
}
public class ReetrantLockDemo {

    public static void main(String[] args) {

        Phone phone = new Phone();
        new Thread(()->{
            phone.sendMessage();
        }).start();

        new Thread(()->{
            phone.sendMessage();
        }).start();
    }
}

 輸出結果:

 

對於RetrantLock同樣可得上述結果:

class Phone{

    Lock lock = new ReentrantLock();

    public void sendMessage(){
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getId() + " \t " + " invoked sendMessage");
            sendEmail();
        }finally {
            lock.unlock();
        }

    }

    public synchronized void sendEmail(){
        try {
            lock.lock();

            System.out.println(Thread.currentThread().getId() + " \t " + " ###### invoked sendEmail");
            System.out.println("----------------------------------- 分割線  -----------------------------------");
            System.out.println();
        }finally {
            lock.unlock();
        }

    }
}
public class ReetrantLockDemo {

    public static void main(String[] args) throws InterruptedException {

        Phone phone = new Phone();

        new Thread(()->{
            phone.sendMessage();
        }).start();

        new Thread(()->{
            phone.sendMessage();
        }).start();
    }
}

 輸出結果同上。

自旋鎖(spinlock)

自旋鎖:是指嘗試獲取鎖的線程不會立即阻塞,而是采用循環的方式去嘗試獲取鎖,這樣的好處是減少線程上下文切換的消耗,缺點是:循環會消耗CPU資源。

我們學習過的CAS原理,就是采用了自旋鎖的思想:

 了解了自旋鎖的原理之后,我們自己實現一個自旋鎖。

public class SpinLockDemo {

    AtomicReference<Thread> atomicReference = new AtomicReference<>();
    public void myLock(){
        System.out.println(Thread.currentThread().getName() + "\t come in");
        Thread thread = Thread.currentThread();
        while (!atomicReference.compareAndSet(null, thread)){

        }
    }

    public void myUnLock(){
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread, null);
        System.out.println(Thread.currentThread().getName() + "\t leave out");
    }
    public static void main(String[] args) {
        SpinLockDemo demo = new SpinLockDemo();

        new Thread(()->{
            demo.myLock();
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            demo.myUnLock();
        }, "AAA").start();

        new Thread(()->{
            demo.myLock();
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            demo.myUnLock();
        }, "BBB").start();
    }
}

 輸出結果:

 

獨占鎖(寫鎖)/共享鎖(讀鎖)/互斥鎖

獨占鎖:指該鎖一次只能被一個線程所持有。對ReentrantLock和synchronized來說,它們都是獨占鎖。

共享鎖:指該鎖可以被多個線程所持有。

對ReentrantReadWriteLock來說,讀鎖是共享鎖,寫鎖是獨占鎖。

讀鎖的共享鎖可以保證並發讀是非常高效的,讀寫,寫讀,寫寫的過程是互斥的。

  也就是說,多個線程同時讀取一個資源類沒有任何問題,所以為了滿足並發量,讀取共享資源應該可以同時進行。但是,如果有一個線程想去寫共享資源,就不應該又其他的線程對資源進行讀或者寫。

總結:讀-讀可共存

     讀-寫不可以共存

   寫-寫不可以共存

代碼實現小例子:

class CacheResource{
    private volatile Map<String, Object> map = new HashMap<>();
    private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();

    public void put(String key, Object value){
        try {
            reentrantReadWriteLock.writeLock().lock();
            System.out.println("--------------------------------------------------------------");
            System.out.println(Thread.currentThread().getId() + " \t " + "正在寫入" + key);

        }finally {
            System.out.println(Thread.currentThread().getId() + " \t " + "寫入成功" + key);
            System.out.println("--------------------------------------------------------------");

            reentrantReadWriteLock.writeLock().unlock();
        }
    }

    public void get(String key){
        try {
            reentrantReadWriteLock.readLock().lock();
            System.out.println(Thread.currentThread().getId() + " \t " + "正在讀取 \t" + key);
        }finally {
            System.out.println(Thread.currentThread().getId() + " \t " + "讀取成功 \t" + key);
            reentrantReadWriteLock.readLock().unlock();
        }

    }
}
public class ReentrantReadWriteLockDemo {

    public static void main(String[] args) {
        CacheResource resource = new CacheResource();

        for (int i = 0; i < 5; i++){
            final int tmp = i;
            new Thread(()->{
                resource.put(tmp + "", "");
            }).start();
        }

        for (int i = 0; i < 5; i++){
            final int tmp = i;
            new Thread(()->{
                resource.get(tmp + "");
            }).start();
        }
    }
}

 

輸出結果:

--------------------------------------------------------------
12      正在寫入1
12      寫入成功1
--------------------------------------------------------------
--------------------------------------------------------------
11      正在寫入0
11      寫入成功0
--------------------------------------------------------------
--------------------------------------------------------------
13      正在寫入2
13      寫入成功2
--------------------------------------------------------------
--------------------------------------------------------------
14      正在寫入3
14      寫入成功3
--------------------------------------------------------------
--------------------------------------------------------------
15      正在寫入4
15      寫入成功4
--------------------------------------------------------------
16      正在讀取     0
16      讀取成功     0
18      正在讀取     2
18      讀取成功     2
19      正在讀取     3
19      讀取成功     3
20      正在讀取     4
20      讀取成功     4
17      正在讀取     1
17      讀取成功     1

Process finished with exit code 0

 到這里,本篇文章就結束了,雖然寫博客會花費一定的時間,但是可以加深自己對知識點的理解,便於日后復習,要堅持。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM