欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java中的自旋鎖與適應(yīng)性自旋鎖的區(qū)別

 更新時間:2023年10月13日 10:30:36   作者:我用漂亮的押韻形容被掠奪一空的愛情  
這篇文章主要介紹了Java中的自旋鎖與適應(yīng)性自旋鎖的區(qū)別,當(dāng)一個線程嘗試去獲取某一把鎖的時候,如果這個鎖此時已經(jīng)被別人獲取(占用),那么此線程就無法獲取到這把鎖,該線程將會等待,間隔一段時間后會再次嘗試獲取,需要的朋友可以參考下

自旋鎖

1、概念:

當(dāng)一個線程嘗試去獲取某一把鎖的時候,如果這個鎖此時已經(jīng)被別人獲取(占用),那么此線程就無法獲取到這把鎖,該線程將會等待,間隔一段時間后會再次嘗試獲取。這種采用循環(huán)加鎖 -> 等待的機制被稱為自旋鎖(spinlock)

2、提出背景

由于在多處理器環(huán)境中某些資源的有限性,有時需要互斥訪問(mutual exclusion),這時候就需要引入鎖的概念,只有獲取了鎖的線程才能夠?qū)Y源進行訪問,由于多線程的核心是CPU的時間分片,所以同一時刻只能有一個線程獲取到鎖。那么就面臨一個問題,那么沒有獲取到鎖的線程應(yīng)該怎么辦?

通常有兩種處理方式:一種是沒有獲取到鎖的線程就一直循環(huán)等待判斷該資源是否已經(jīng)釋放鎖,這種鎖叫做自旋鎖,它不用將線程阻塞起來(NON-BLOCKING);還有一種處理方式就是把自己阻塞起來,等待重新調(diào)度請求,這種叫做互斥鎖。

3、自旋鎖的原理

自旋鎖的原理比較簡單,如果持有鎖的線程能在短時間內(nèi)釋放鎖資源,那么那些等待競爭鎖的線程就不需要做內(nèi)核態(tài)和用戶態(tài)之間的切換進入阻塞狀態(tài),它們只需要等一等(自旋),等到持有鎖的線程釋放鎖之后即可獲取,這樣就避免了用戶進程和內(nèi)核切換的消耗。

因為自旋鎖避免了操作系統(tǒng)進程調(diào)度和線程切換,所以自旋鎖通常適用在時間比較短的情況下。由于這個原因,操作系統(tǒng)的內(nèi)核經(jīng)常使用自旋鎖。但是,如果長時間上鎖的話,自旋鎖會非常耗費性能,它阻止了其他線程的運行和調(diào)度。線程持有鎖的時間越長,則持有該鎖的線程將被 OS(Operating System) 調(diào)度程序中斷的風(fēng)險越大。如果發(fā)生中斷情況,那么其他線程將保持旋轉(zhuǎn)狀態(tài)(反復(fù)嘗試獲取鎖),而持有該鎖的線程并不打算釋放鎖,這樣導(dǎo)致的是結(jié)果是無限期推遲,直到持有鎖的線程可以完成并釋放它為止。

解決上面這種情況一個很好的方式是給自旋鎖設(shè)定一個自旋時間,等時間一到立即釋放自旋鎖。自旋鎖的目的是占著CPU資源不進行釋放,等到獲取鎖立即進行處理。但是如何去選擇自旋時間呢?如果自旋執(zhí)行時間太長,會有大量的線程處于自旋狀態(tài)占用 CPU 資源,進而會影響整體系統(tǒng)的性能。因此自旋的周期選的額外重要!JDK在1.6 引入了適應(yīng)性自旋鎖,適應(yīng)性自旋鎖意味著自旋時間不是固定的了,而是由前一次在同一個鎖上的自旋時間以及鎖擁有的狀態(tài)來決定,基本認為一個線程上下文切換的時間是最佳的一個時間。

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2F4aWFvYm9nZQ==,size_16,color_FFFFFF,t_70

4、 自旋鎖的優(yōu)缺點

優(yōu)點:自旋鎖盡可能的減少線程的阻塞,這對于鎖的競爭不激烈,且占用鎖時間非常短的代碼塊來說性能能大幅度的提升,因為自旋的消耗會小于線程阻塞掛起再喚醒的操作的消耗,這些操作會導(dǎo)致線程發(fā)生兩次上下文切換!

缺點:但是如果鎖的競爭激烈,或者持有鎖的線程需要長時間占用鎖執(zhí)行同步塊,這時候就不適合使用自旋鎖了,因為自旋鎖在獲取鎖前一直都是占用 cpu 做無用功,占著 XX 不 XX,同時有大量線程在競爭一個鎖,會導(dǎo)致獲取鎖的時間很長,線程自旋的消耗大于線程阻塞掛起操作的消耗,其它需要 cpu 的線程又不能獲取到 cpu,造成 cpu 的浪費。所以這種情況下我們要關(guān)閉自旋鎖。

5、自旋鎖開啟

雖然在JDK1.4.2的時候就引入了自旋鎖,但是需要使用“-XX:+UseSpinning”參數(shù)來開啟。在到了JDK1.6以后,就已經(jīng)是默認開啟了。

舉個栗子:

public class SpinLockTest {
    /**
     * 持有鎖的線程,null表示鎖未被線程持有
     */
    private AtomicReference<Thread> ref = new AtomicReference<>();
    public void lock(){
        Thread currentThread = Thread.currentThread();
        while(!ref.compareAndSet(null, currentThread)){
            //當(dāng)ref為null的時候compareAndSet返回true,反之為false
            //通過循環(huán)不斷的自旋判斷鎖是否被其他線程持有
        }
    }
    public void unLock() {
        Thread cur = Thread.currentThread();
        if(ref.get() != cur){
            //exception ...
        }
        ref.set(null);
    }
}
//自旋鎖測試
public class SpinLockTestTest {
    static int count = 0;
    @Test
    public void spinLockTest() throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        CountDownLatch countDownLatch = new CountDownLatch(100);
        SpinLockTest2 spinLockTest2 = new SpinLockTest2();
        for (int i = 0; i < 100; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    spinLockTest2.lock();
                    ++count;
                    spinLockTest2.unLock();
                    countDownLatch.countDown();
                }
            });
        }
        countDownLatch.await();
        System.out.println (count);
   }
}

通過上面的代碼可以看出,自旋就是在循環(huán)判斷條件是否滿足,那么會有什么問題嗎?如果鎖被占用很長時間的話,自旋的線程等待的時間也會變長,白白浪費掉處理器資源。因此在JDK中,自旋操作默認10次,我們可以通過參數(shù)“-XX:PreBlockSpin”來設(shè)置,當(dāng)超過來此參數(shù)的值,則會使用傳統(tǒng)的線程掛起方式來等待鎖釋放。

自適應(yīng)自旋鎖

隨著JDK的更新,在1.6的時候,又出現(xiàn)了一個叫做“自適應(yīng)自旋鎖”的玩意。它的出現(xiàn)使得自旋操作變得聰明起來,不再跟之前一樣死板。所謂的“自適應(yīng)”意味著對于同一個鎖對象,線程的自旋時間是根據(jù)上一個持有該鎖的線程的自旋時間以及狀態(tài)來確定的。例如對于A鎖對象來說,如果一個線程剛剛通過自旋獲得到了鎖,并且該線程也在運行中,那么JVM會認為此次自旋操作也是有很大的機會可以拿到鎖,因此它會讓自旋的時間相對延長。但是如果對于B鎖對象自旋操作很少成功的話,JVM甚至可能直接忽略自旋操作。因此,自適應(yīng)自旋鎖是一個更加智能,對我們的業(yè)務(wù)性能更加友好的一個鎖。

總結(jié)

自旋鎖是為了提高資源的使用頻率而出現(xiàn)的一種鎖,自旋鎖說的是線程獲取鎖的時候,如果鎖被其他線程持有,則當(dāng)前線程將循環(huán)等待,直到獲取到鎖。

自旋鎖在等待期間不會睡眠或者釋放自己的線程。自旋鎖不適用于長時間持有CPU的情況,這會加劇系統(tǒng)的負擔(dān),為了解決這種情況,需要設(shè)定自旋周期,那么自旋周期的設(shè)定也是一門學(xué)問。

在自旋鎖中 另有三種常見的鎖形式:TicketLock、CLHlock和MCSlock

這里直接舉栗子:

  • TicketLock 是一種同步機制或鎖定算法,它是一種自旋鎖,它使用ticket 來控制線程執(zhí)行順序。
/**
 * @Description TicketLock 是一種同步機制或鎖定算法,它是一種自旋鎖,它使用ticket 來控制線程執(zhí)行順序。
 *
 * TicketLock 是基于先進先出(FIFO) 隊列的機制。
 * 它增加了鎖的公平性,其設(shè)計原則如下:TicketLock 中有兩個 int 類型的數(shù)值,
 * 開始都是0,第一個值是隊列ticket(隊列票據(jù)), 第二個值是 出隊(票據(jù))。
 * 隊列票據(jù)是線程在隊列中的位置,而出隊票據(jù)是現(xiàn)在持有鎖的票證的隊列位置。
 * 可能有點模糊不清,簡單來說,就是隊列票據(jù)是你取票號的位置,出隊票據(jù)是你距離叫號的位置。
 *
 *
 * @Author zhoumm
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2020-05-29
 */
//這個設(shè)計是有問題的,因為獲得自己的號碼之后,是可以對號碼進行更改的,這就造成系統(tǒng)紊亂,鎖不能及時釋放。
//需要有一個能確保每個人按會著自己號碼排隊辦業(yè)務(wù)的角色
public class TicketLock {
    // 隊列票據(jù)(當(dāng)前排隊號碼)
    private AtomicInteger queueNum = new AtomicInteger();
    // 出隊票據(jù)(當(dāng)前需等待號碼)
    private AtomicInteger dueueNum = new AtomicInteger();
    // 獲取鎖:如果獲取成功,返回當(dāng)前線程的排隊號
    public int lock(){
        int currentTicketNum = dueueNum.incrementAndGet();
        while (currentTicketNum != queueNum.get()){
            // doSomething...
        }
        return currentTicketNum;
    }
    // 釋放鎖:傳入當(dāng)前排隊的號碼
    public void unLock(int ticketNum){
        queueNum.compareAndSet(ticketNum,ticketNum + 1);
    }
    //改進后,這就不再需要返回值,辦業(yè)務(wù)的時候,要將當(dāng)前的這一個號碼緩存起來,在辦完業(yè)務(wù)后,需要釋放緩存的這條票據(jù)。
    //缺點:雖然解決了公平性的問題,但是多處理器系統(tǒng)上,每個進程/線程占用的處理器都在讀寫同一個變量queueNum ,
    // 每次讀寫操作都必須在多個處理器緩存之間進行緩存同步,這會導(dǎo)致繁重的系統(tǒng)總線和內(nèi)存的流量,大大降低系統(tǒng)整體的性能。
    //為了解決這個問題,MCSLock 和 CLHLock 應(yīng)運而生
    public class TicketLock2 {
        // 隊列票據(jù)(當(dāng)前排隊號碼)
        private AtomicInteger queueNum = new AtomicInteger();
        // 出隊票據(jù)(當(dāng)前需等待號碼)
        private AtomicInteger dueueNum = new AtomicInteger();
        //線程內(nèi)部的存儲類,可以在指定線程內(nèi)存儲數(shù)據(jù),數(shù)據(jù)存儲以后,只有指定線程可以得到存儲數(shù)據(jù)
        private ThreadLocal<Integer> ticketLocal = new ThreadLocal<>();
        public void lock(){
            int currentTicketNum = dueueNum.incrementAndGet();
            // 獲取鎖的時候,將當(dāng)前線程的排隊號保存起來
            ticketLocal.set(currentTicketNum);
            while (currentTicketNum != queueNum.get()){
                // doSomething...
            }
        }
        // 釋放鎖:從排隊緩沖池中取
        public void unLock(){
            Integer currentTicket = ticketLocal.get();
            queueNum.compareAndSet(currentTicket,currentTicket + 1);
        }
    }
}
  • TicketLock 是基于隊列的,那么 CLHLock 就是基于鏈表設(shè)計的
/**
 * @Description
 * TicketLock 是基于隊列的,那么 CLHLock 就是基于鏈表設(shè)計的
 *
 * CLH 是一種基于鏈表的可擴展,高性能,公平的自旋鎖,申請線程只能在本地變量上自旋,
 * 它會不斷輪詢前驅(qū)的狀態(tài),如果發(fā)現(xiàn)前驅(qū)釋放了鎖就結(jié)束自旋。
 *
 *
 * @Author zhoumm
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2020-05-29
 */
public class CLHLock {
    public static class CLHNode{
        private volatile boolean isLocked = true;
    }
    // 尾部節(jié)點
    private volatile CLHNode tail;
    private static final ThreadLocal<CLHNode> LOCAL = new ThreadLocal<>();
    private static final AtomicReferenceFieldUpdater<CLHLock,CLHNode> UPDATER =
            AtomicReferenceFieldUpdater.newUpdater(CLHLock.class,CLHNode.class,"tail");
    public void lock(){
        // 新建節(jié)點并將節(jié)點與當(dāng)前線程保存起來
        CLHNode node = new CLHNode();
        LOCAL.set(node);
        // 將新建的節(jié)點設(shè)置為尾部節(jié)點,并返回舊的節(jié)點(原子操作),這里舊的節(jié)點實際上就是當(dāng)前節(jié)點的前驅(qū)節(jié)點
        CLHNode preNode = UPDATER.getAndSet(this,node);
        if(preNode != null){
            // 前驅(qū)節(jié)點不為null表示當(dāng)鎖被其他線程占用,通過不斷輪詢判斷前驅(qū)節(jié)點的鎖標(biāo)志位等待前驅(qū)節(jié)點釋放鎖
            while (preNode.isLocked){
            }
            preNode = null;
            LOCAL.set(node);
        }
        // 如果不存在前驅(qū)節(jié)點,表示該鎖沒有被其他線程占用,則當(dāng)前線程獲得鎖
    }
    public void unlock() {
        // 獲取當(dāng)前線程對應(yīng)的節(jié)點
        CLHNode node = LOCAL.get();
        // 如果tail節(jié)點等于node,則將tail節(jié)點更新為null,同時將node的lock狀態(tài)職位false,表示當(dāng)前線程釋放了鎖
        if (!UPDATER.compareAndSet(this, node, null)) {
            node.isLocked = false;
        }
        node = null;
    }
}
  • MCS Spinlock 是一種基于鏈表的可擴展、高性能、公平的自旋鎖,申請線程只在本地變量上自旋,直接前驅(qū)負責(zé)通知其結(jié)束自旋,從而極大地減少了不必要的處理器緩存同步的次數(shù),降低了總線和內(nèi)存的開銷
/**
 * @Description
 * MCS Spinlock 是一種基于鏈表的可擴展、高性能、公平的自旋鎖,申請線程只在本地變量上自旋,
 * 直接前驅(qū)負責(zé)通知其結(jié)束自旋,從而極大地減少了不必要的處理器緩存同步的次數(shù),降低了總線和內(nèi)存的開銷。
 *
 * 總結(jié):
 * 1.都是基于鏈表,不同的是CLHLock是基于隱式鏈表,沒有真正的后續(xù)節(jié)點屬性,MCSLock是顯示鏈表,有一個指向后續(xù)節(jié)點的屬性。
 * 2.將獲取鎖的線程狀態(tài)借助節(jié)點(node)保存,每個線程都有一份獨立的節(jié)點,這樣就解決了TicketLock多處理器緩存同步的問題。
 * @Author zhoumm
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2020-05-29
 */
public class MCSLock {
    public static class MCSNode {
        volatile MCSNode next;
        volatile boolean isLocked = true;
    }
    private static final ThreadLocal<MCSNode> NODE = new ThreadLocal<>();
    // 隊列
    @SuppressWarnings("unused")
    private volatile MCSNode queue;
    private static final AtomicReferenceFieldUpdater<MCSLock,MCSNode> UPDATE =
            AtomicReferenceFieldUpdater.newUpdater(MCSLock.class,MCSNode.class,"queue");
    public void lock(){
        // 創(chuàng)建節(jié)點并保存到ThreadLocal中
        MCSNode currentNode = new MCSNode();
        NODE.set(currentNode);
        // 將queue設(shè)置為當(dāng)前節(jié)點,并且返回之前的節(jié)點
        MCSNode preNode = UPDATE.getAndSet(this, currentNode);
        if (preNode != null) {
            // 如果之前節(jié)點不為null,表示鎖已經(jīng)被其他線程持有
            preNode.next = currentNode;
            // 循環(huán)判斷,直到當(dāng)前節(jié)點的鎖標(biāo)志位為false
            while (currentNode.isLocked) {
            }
        }
    }
    public void unlock() {
        MCSNode currentNode = NODE.get();
        // next為null表示沒有正在等待獲取鎖的線程
        if (currentNode.next == null) {
            // 更新狀態(tài)并設(shè)置queue為null
            if (UPDATE.compareAndSet(this, currentNode, null)) {
                // 如果成功了,表示queue==currentNode,即當(dāng)前節(jié)點后面沒有節(jié)點了
                return;
            } else {
                // 如果不成功,表示queue!=currentNode,即當(dāng)前節(jié)點后面多了一個節(jié)點,表示有線程在等待
                // 如果當(dāng)前節(jié)點的后續(xù)節(jié)點為null,則需要等待其不為null(參考加鎖方法)
                while (currentNode.next == null) {
                }
            }
        } else {
            // 如果不為null,表示有線程在等待獲取鎖,此時將等待線程對應(yīng)的節(jié)點鎖狀態(tài)更新為false,同時將當(dāng)前線程的后繼節(jié)點設(shè)為null
            currentNode.next.isLocked = false;
            currentNode.next = null;
        }
    }
}

到此這篇關(guān)于Java中的自旋鎖與適應(yīng)性自旋鎖的區(qū)別的文章就介紹到這了,更多相關(guān)自旋鎖與適應(yīng)性自旋鎖區(qū)別內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot一個接口多個實現(xiàn)類的調(diào)用方式總結(jié)

    SpringBoot一個接口多個實現(xiàn)類的調(diào)用方式總結(jié)

    這篇文章主要介紹了SpringBoot一個接口多個實現(xiàn)類的調(diào)用方式,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2024-01-01
  • Java貪吃蛇游戲完善版

    Java貪吃蛇游戲完善版

    這篇文章主要為大家詳細介紹了Java貪吃蛇游戲完善版,支持菜單操作,鍵盤監(jiān)聽,可加速,減速,統(tǒng)計得分等功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-04-04
  • 深入理解Spring中RabbitMQ的Channel

    深入理解Spring中RabbitMQ的Channel

    這篇文章主要介紹了深入理解Spring中RabbitMQ的Channel,在RabbitMq中,channel表示邏輯連接或者叫虛擬連接,是棣屬于TCP連接的,一個TCP連接里可以創(chuàng)建多個channel,在Rabbit MQ里,消息的發(fā)送和接收都是基于channel的,需要的朋友可以參考下
    2023-08-08
  • Java中讀取文件轉(zhuǎn)換為字符串的方法

    Java中讀取文件轉(zhuǎn)換為字符串的方法

    今天小編就為大家分享一篇Java中讀取文件轉(zhuǎn)換為字符串的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-07-07
  • Java重寫equals及hashcode方法流程解析

    Java重寫equals及hashcode方法流程解析

    這篇文章主要介紹了Java重寫equals及hashcode方法流程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-04-04
  • 使用JAVA實現(xiàn)http通信詳解

    使用JAVA實現(xiàn)http通信詳解

    本文給大家匯總介紹了幾種java實現(xiàn)http通訊的方法,非常的簡單實用,有需要的小伙伴可以參考下。
    2015-08-08
  • Java 讀取圖片的mimeType的方法

    Java 讀取圖片的mimeType的方法

    本篇文章主要介紹了Java 讀取圖片的mimeType的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-01-01
  • Java DecimalFormat 保留小數(shù)位及四舍五入的陷阱介紹

    Java DecimalFormat 保留小數(shù)位及四舍五入的陷阱介紹

    這篇文章主要介紹了Java DecimalFormat 保留小數(shù)位及四舍五入的陷阱,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • 編譯期動態(tài)替換三方包中的Class文件過程詳解

    編譯期動態(tài)替換三方包中的Class文件過程詳解

    這篇文章主要為大家介紹了編譯期動態(tài)替換三方包中的Class文件過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-03-03
  • SSM框架搭建圖文教程(推薦)

    SSM框架搭建圖文教程(推薦)

    下面小編就為大家?guī)硪黄猄SM框架搭建圖文教程(推薦)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-09-09

最新評論