最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java實(shí)現(xiàn)手寫自旋鎖的示例代碼

 更新時(shí)間:2022年08月17日 16:55:32   作者:一無是處的研究僧  
自旋鎖是專為防止多處理器并發(fā)而引入的一種鎖,它在內(nèi)核中大量應(yīng)用于中斷處理等部分。本文將用Java實(shí)現(xiàn)手寫自旋鎖,需要的可以參考一下

前言

我們在寫并發(fā)程序的時(shí)候,一個(gè)非常常見的需求就是保證在某一個(gè)時(shí)刻只有一個(gè)線程執(zhí)行某段代碼,像這種代碼叫做臨界區(qū),而通常保證一個(gè)時(shí)刻只有一個(gè)線程執(zhí)行臨界區(qū)的代碼的方法就是鎖。在本篇文章當(dāng)中我們將會仔細(xì)分析和學(xué)習(xí)自旋鎖,所謂自旋鎖就是通過while循環(huán)實(shí)現(xiàn)的,讓拿到鎖的線程進(jìn)入臨界區(qū)執(zhí)行代碼,讓沒有拿到鎖的線程一直進(jìn)行while死循環(huán),這其實(shí)就是線程自己“旋”在while循環(huán)了,因而這種鎖就叫做自旋鎖。

自旋鎖

原子性

在談自旋鎖之前就不得不談原子性了。所謂原子性簡單說來就是一個(gè)一個(gè)操作要么不做要么全做,全做的意思就是在操作的過程當(dāng)中不能夠被中斷,比如說對變量data進(jìn)行加一操作,有以下三個(gè)步驟:

  • 將data從內(nèi)存加載到寄存器。
  • 將data這個(gè)值加一。
  • 將得到的結(jié)果寫回內(nèi)存。

原子性就表示一個(gè)線程在進(jìn)行加一操作的時(shí)候,不能夠被其他線程中斷,只有這個(gè)線程執(zhí)行完這三個(gè)過程的時(shí)候其他線程才能夠操作數(shù)據(jù)data。

我們現(xiàn)在用代碼體驗(yàn)一下,在Java當(dāng)中我們可以使用AtomicInteger進(jìn)行對整型數(shù)據(jù)的原子操作:

import java.util.concurrent.atomic.AtomicInteger;
 
public class AtomicDemo {
 
  public static void main(String[] args) throws InterruptedException {
    AtomicInteger data = new AtomicInteger();
    data.set(0); // 將數(shù)據(jù)初始化位0
    Thread t1 = new Thread(() -> {
      for (int i = 0; i < 100000; i++) {
        data.addAndGet(1); // 對數(shù)據(jù) data 進(jìn)行原子加1操作
      }
    });
    Thread t2 = new Thread(() -> {
      for (int i = 0; i < 100000; i++) {
        data.addAndGet(1);// 對數(shù)據(jù) data 進(jìn)行原子加1操作
      }
    });
    // 啟動兩個(gè)線程
    t1.start();
    t2.start();
    // 等待兩個(gè)線程執(zhí)行完成
    t1.join();
    t2.join();
    // 打印最終的結(jié)果
    System.out.println(data); // 200000
  }
}

從上面的代碼分析可以知道,如果是一般的整型變量如果兩個(gè)線程同時(shí)進(jìn)行操作的時(shí)候,最終的結(jié)果是會小于200000。

我們現(xiàn)在來模擬一下一般的整型變量出現(xiàn)問題的過程:

主內(nèi)存data的初始值等于0,兩個(gè)線程得到的data初始值都等于0。

現(xiàn)在線程一將data加一,然后線程一將data的值同步回主內(nèi)存,整個(gè)內(nèi)存的數(shù)據(jù)變化如下:

現(xiàn)在線程二data加一,然后將data的值同步回主內(nèi)存(將原來主內(nèi)存的值覆蓋掉了):

我們本來希望data的值在經(jīng)過上面的變化之后變成2,但是線程二覆蓋了我們的值,因此在多線程情況下,會使得我們最終的結(jié)果變小。

但是在上面的程序當(dāng)中我們最終的輸出結(jié)果是等于20000的,這是因?yàn)榻odata進(jìn)行+1的操作是原子的不可分的,在操作的過程當(dāng)中其他線程是不能對data進(jìn)行操作的。這就是原子性帶來的優(yōu)勢。

自己動手寫自旋鎖

AtomicInteger類

現(xiàn)在我們已經(jīng)了解了原子性的作用了,我們現(xiàn)在來了解AtomicInteger類的另外一個(gè)原子性的操作——compareAndSet,這個(gè)操作叫做比較并交換(CAS),他具有原子性。

public static void main(String[] args) {
  AtomicInteger atomicInteger = new AtomicInteger();
  atomicInteger.set(0);
  atomicInteger.compareAndSet(0, 1);
}

compareAndSet函數(shù)的意義:首先會比較第一個(gè)參數(shù)(對應(yīng)上面的代碼就是0)和atomicInteger的值,如果相等則進(jìn)行交換,也就是將atomicInteger的值設(shè)置為第二個(gè)參數(shù)(對應(yīng)上面的代碼就是1),如果這些操作成功,那么compareAndSet函數(shù)就返回true,如果操作失敗則返回false,操作失敗可能是因?yàn)榈谝粋€(gè)參數(shù)的值(期望值)和atomicInteger不相等,如果相等也可能因?yàn)樵诟腶tomicInteger的值的時(shí)候失敗(因?yàn)榭赡苡卸鄠€(gè)線程在操作,因?yàn)樵有缘拇嬖?,只能有一個(gè)線程操作成功)。

自旋鎖實(shí)現(xiàn)原理

我們可以使用AtomicInteger類實(shí)現(xiàn)自旋鎖,我們可以用0這個(gè)值表示未上鎖,1這個(gè)值表示已經(jīng)上鎖了。

AtomicInteger類的初始值為0。

在上鎖時(shí),我們可以使用代碼atomicInteger.compareAndSet(0, 1)進(jìn)行實(shí)現(xiàn),我們在前面已經(jīng)提到了只能夠有一個(gè)線程完成這個(gè)操作,也就是說只能有一個(gè)線程調(diào)用這行代碼然后返回true其余線程都返回false,這些返回false的線程不能夠進(jìn)入臨界區(qū),因此我們需要這些線程停在atomicInteger.compareAndSet(0, 1)這行代碼不能夠往下執(zhí)行,我們可以使用while循環(huán)讓這些線程一直停在這里while (!value.compareAndSet(0, 1));,只有返回true的線程才能夠跳出循環(huán),其余線程都會一直在這里循環(huán),我們稱這種行為叫做自旋,這種鎖因而也被叫做自旋鎖。

線程在出臨界區(qū)的時(shí)候需要重新將鎖的狀態(tài)調(diào)整為未上鎖的上狀態(tài),我們使用代碼value.compareAndSet(1, 0);就可以實(shí)現(xiàn),將鎖的狀態(tài)還原為未上鎖的狀態(tài),這樣其他的自旋的線程就可以拿到鎖,然后進(jìn)入臨界區(qū)了。

自旋鎖代碼實(shí)現(xiàn)

import java.util.concurrent.atomic.AtomicInteger;
 
public class SpinLock {
    
  // 0 表示未上鎖狀態(tài)
  // 1 表示上鎖狀態(tài)
  protected AtomicInteger value;
 
  public SpinLock() {
    this.value = new AtomicInteger();
    // 設(shè)置 value 的初始值為0 表示未上鎖的狀態(tài)
    this.value.set(0);
  }
 
  public void lock() {
    // 進(jìn)行自旋操作
    while (!value.compareAndSet(0, 1));
  }
 
  public void unlock() {
    // 將鎖的狀態(tài)設(shè)置為未上鎖狀態(tài)
    value.compareAndSet(1, 0);
  }
 
}

上面就是我們自己實(shí)現(xiàn)的自旋鎖的代碼,這看起來實(shí)在太簡單了,但是它確實(shí)幫助我們實(shí)現(xiàn)了一個(gè)鎖,而且能夠在真實(shí)場景進(jìn)行使用的,我們現(xiàn)在用代碼對上面我們寫的鎖進(jìn)行測試。

測試程序:

public class SpinLockTest {
 
  public static int data;
  public static SpinLock lock = new SpinLock();
 
  public static void add() {
    for (int i = 0; i < 100000; i++) {
      // 上鎖 只能有一個(gè)線程執(zhí)行 data++ 操作 其余線程都只能進(jìn)行while循環(huán)
      lock.lock();
      data++;
      lock.unlock();
    }
  }
 
  public static void main(String[] args) throws InterruptedException {
    Thread[] threads = new Thread[100];
    // 設(shè)置100個(gè)線程
    for (int i = 0; i < 100; i ++) {
      threads[i] = new Thread(SpinLockTest::add);
    }
    // 啟動一百個(gè)線程
    for (int i = 0; i < 100; i++) {
      threads[i].start();
    }
    // 等待這100個(gè)線程執(zhí)行完成
    for (int i = 0; i < 100; i++) {
      threads[i].join();
    }
    System.out.println(data); // 10000000
  }
}

在上面的代碼單中,我們使用100個(gè)線程,然后每個(gè)線程循環(huán)執(zhí)行100000data++操作,上面的代碼最后輸出的結(jié)果是10000000,和我們期待的結(jié)果是相等的,這就說明我們實(shí)現(xiàn)的自旋鎖是正確的。

自己動手寫可重入自旋鎖

可重入自旋鎖

在上面實(shí)現(xiàn)的自旋鎖當(dāng)中已經(jīng)可以滿足一些我們的基本需求了,就是一個(gè)時(shí)刻只能夠有一個(gè)線程執(zhí)行臨界區(qū)的代碼。但是上面的的代碼并不能夠滿足重入的需求,也就是說上面寫的自旋鎖并不是一個(gè)可重入的自旋鎖,事實(shí)上在上面實(shí)現(xiàn)的自旋鎖當(dāng)中重入的話就會產(chǎn)生死鎖。

我們通過一份代碼來模擬上面重入產(chǎn)生死鎖的情況:

public static void add(int state) throws InterruptedException {
  TimeUnit.SECONDS.sleep(1);
  if (state <= 3) {
    lock.lock();
    System.out.println(Thread.currentThread().getName() + "\t進(jìn)入臨界區(qū) state = " + state);
    for (int i = 0; i < 10; i++)
      data++;
    add(state + 1); // 進(jìn)行遞歸重入 重入之前鎖狀態(tài)已經(jīng)是1了 因?yàn)檫@個(gè)線程進(jìn)入了臨界區(qū)
    lock.unlock();
  }
}

在上面的代碼當(dāng)中加入我們傳入的參數(shù)state的值為1,那么在線程執(zhí)行for循環(huán)之后再次遞歸調(diào)用add函數(shù)的話,那么state的值就變成了2。

if條件仍然滿足,這個(gè)線程也需要重新獲得鎖,但是此時(shí)鎖的狀態(tài)是1,這個(gè)線程已經(jīng)獲得過一次鎖了,但是自旋鎖期待的鎖的狀態(tài)是0,因?yàn)橹挥羞@樣他才能夠再次獲得鎖,進(jìn)入臨界區(qū),但是現(xiàn)在鎖的狀態(tài)是1,也就是說雖然這個(gè)線程獲得過一次鎖,但是它也會一直進(jìn)行while循環(huán)而且永遠(yuǎn)都出不來了,這樣就形成了死鎖了。

可重入自旋鎖思想

針對上面這種情況我們需要實(shí)現(xiàn)一個(gè)可重入的自旋鎖,我們的思想大致如下:

  • 在我們實(shí)現(xiàn)的自旋鎖當(dāng)中,我們可以增加兩個(gè)變量,owner一個(gè)用于存當(dāng)前擁有鎖的線程,count一個(gè)記錄當(dāng)前線程進(jìn)入鎖的次數(shù)。
  • 如果線程獲得鎖,owner = Thread.currentThread()并且count = 1。
  • 當(dāng)線程下次再想獲取鎖的時(shí)候,首先先看owner是不是指向自己,則一直進(jìn)行循環(huán)操作,如果是則直接進(jìn)行count++操作,然后就可以進(jìn)入臨界區(qū)了。
  • 我們在出臨界區(qū)的時(shí)候,如果count大于一的話,說明這個(gè)線程重入了這把鎖,因此不能夠直接將鎖設(shè)置為0也就是未上鎖的狀態(tài),這種情況直接進(jìn)行count--操作,如果count等于1的話,說明線程當(dāng)前的狀態(tài)不是重入狀態(tài)(可能是重入之后遞歸返回了),因此在出臨界區(qū)之前需要將鎖的狀態(tài)設(shè)置為0,也就是沒上鎖的狀態(tài),好讓其他線程能夠獲取鎖。

可重入鎖代碼實(shí)現(xiàn)

實(shí)現(xiàn)的可重入鎖代碼如下:

public class ReentrantSpinLock extends SpinLock {
 
  private Thread owner;
  private int count;
 
  @Override
  public void lock() {
    if (owner == null || owner != Thread.currentThread()) {
      while (!value.compareAndSet(0, 1));
      owner = Thread.currentThread();
      count = 1;
    }else {
      count++;
    }
 
  }
 
  @Override
  public void unlock() {
    if (count == 1) {
      count = 0;
      value.compareAndSet(1, 0);
    }else
      count--;
  }
}

下面我們通過一個(gè)遞歸程序去驗(yàn)證我們寫的可重入的自旋鎖是否能夠成功工作。

測試程序:

import java.util.concurrent.TimeUnit;
 
public class ReentrantSpinLockTest {
 
  public static int data;
  public static ReentrantSpinLock lock = new ReentrantSpinLock();
 
  public static void add(int state) throws InterruptedException {
    TimeUnit.SECONDS.sleep(1);
    if (state <= 3) {
      lock.lock();
      System.out.println(Thread.currentThread().getName() + "\t進(jìn)入臨界區(qū) state = " + state);
      for (int i = 0; i < 10; i++)
        data++;
      add(state + 1);
      lock.unlock();
    }
  }
 
  public static void main(String[] args) throws InterruptedException {
    Thread[] threads = new Thread[10];
    for (int i = 0; i < 10; i++) {
      threads[i] = new Thread(new Thread(() -> {
        try {
          ReentrantSpinLockTest.add(1);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }, String.valueOf(i)));
    }
    for (int i = 0; i < 10; i++) {
      threads[i].start();
    }
    for (int i = 0; i < 10; i++) {
      threads[i].join();
    }
    System.out.println(data);
  }
}

上面程序的輸出:

Thread-3    進(jìn)入臨界區(qū) state = 1
Thread-3    進(jìn)入臨界區(qū) state = 2
Thread-3    進(jìn)入臨界區(qū) state = 3
Thread-0    進(jìn)入臨界區(qū) state = 1
Thread-0    進(jìn)入臨界區(qū) state = 2
Thread-0    進(jìn)入臨界區(qū) state = 3
Thread-9    進(jìn)入臨界區(qū) state = 1
Thread-9    進(jìn)入臨界區(qū) state = 2
Thread-9    進(jìn)入臨界區(qū) state = 3
Thread-4    進(jìn)入臨界區(qū) state = 1
Thread-4    進(jìn)入臨界區(qū) state = 2
Thread-4    進(jìn)入臨界區(qū) state = 3
Thread-7    進(jìn)入臨界區(qū) state = 1
Thread-7    進(jìn)入臨界區(qū) state = 2
Thread-7    進(jìn)入臨界區(qū) state = 3
Thread-8    進(jìn)入臨界區(qū) state = 1
Thread-8    進(jìn)入臨界區(qū) state = 2
Thread-8    進(jìn)入臨界區(qū) state = 3
Thread-5    進(jìn)入臨界區(qū) state = 1
Thread-5    進(jìn)入臨界區(qū) state = 2
Thread-5    進(jìn)入臨界區(qū) state = 3
Thread-2    進(jìn)入臨界區(qū) state = 1
Thread-2    進(jìn)入臨界區(qū) state = 2
Thread-2    進(jìn)入臨界區(qū) state = 3
Thread-6    進(jìn)入臨界區(qū) state = 1
Thread-6    進(jìn)入臨界區(qū) state = 2
Thread-6    進(jìn)入臨界區(qū) state = 3
Thread-1    進(jìn)入臨界區(qū) state = 1
Thread-1    進(jìn)入臨界區(qū) state = 2
Thread-1    進(jìn)入臨界區(qū) state = 3
300

從上面的輸出結(jié)果我們就可以知道,當(dāng)一個(gè)線程能夠獲取鎖的時(shí)候他能夠進(jìn)行重入,而且最終輸出的結(jié)果也是正確的,因此驗(yàn)證了我們寫了可重入自旋鎖是有效的!

總結(jié)

在本篇文章當(dāng)中主要給大家介紹了自旋鎖和可重入自旋鎖的原理,并且實(shí)現(xiàn)了一遍,其實(shí)代碼還是比較簡單關(guān)鍵需要大家將這其中的邏輯理清楚:

所謂自旋鎖就是通過while循環(huán)實(shí)現(xiàn)的,讓拿到鎖的線程進(jìn)入臨界區(qū)執(zhí)行代碼,讓沒有拿到鎖的線程一直進(jìn)行while死循環(huán)。

可重入的含義就是一個(gè)線程已經(jīng)競爭到了一個(gè)鎖,在競爭到這個(gè)鎖之后又一次有重入臨界區(qū)代碼的需求,如果能夠保證這個(gè)線程能夠重新進(jìn)入臨界區(qū),這就叫可重入。

我們在實(shí)現(xiàn)自旋鎖的時(shí)候使用的是AtomicInteger類,并且我們使用0和1這兩個(gè)數(shù)值用于表示無鎖和鎖被占用兩個(gè)狀態(tài),在獲取鎖的時(shí)候使用while循環(huán)不斷進(jìn)行CAS操作,直到操作成功返回true,在釋放鎖的時(shí)候使用CAS將鎖的狀態(tài)從1變成0。

實(shí)現(xiàn)可重入鎖最重要的一點(diǎn)就是需要記錄是那個(gè)線程獲得了鎖,同時(shí)還需要記錄獲取了幾次鎖,因?yàn)槲覀冊诮怄i的時(shí)候需要進(jìn)行判斷,之后count = 1的情況才能將鎖的狀態(tài)從1設(shè)置成0。

到此這篇關(guān)于Java實(shí)現(xiàn)手寫自旋鎖的示例代碼的文章就介紹到這了,更多相關(guān)Java自旋鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java實(shí)現(xiàn)掃雷游戲控制臺版

    java實(shí)現(xiàn)掃雷游戲控制臺版

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)掃雷游戲控制臺版,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-04-04
  • SpringTask-Timer實(shí)現(xiàn)定時(shí)任務(wù)的詳細(xì)代碼

    SpringTask-Timer實(shí)現(xiàn)定時(shí)任務(wù)的詳細(xì)代碼

    在項(xiàng)目中開發(fā)定時(shí)任務(wù)應(yīng)該一種比較常見的需求,今天通過示例代碼給大家講解SpringTask-Timer實(shí)現(xiàn)定時(shí)任務(wù)的相關(guān)知識,感興趣的朋友一起看看吧
    2024-06-06
  • Mybatis RowBounds 限制查詢條數(shù)的實(shí)現(xiàn)代碼

    Mybatis RowBounds 限制查詢條數(shù)的實(shí)現(xiàn)代碼

    Oracle 數(shù)據(jù)庫查詢增加RowBounds限制查詢條數(shù),默認(rèn)是0到1000條。下面給大家分享Mybatis RowBounds 限制查詢條數(shù)的實(shí)現(xiàn)代碼,需要的朋友參考下吧
    2016-11-11
  • java swing中實(shí)現(xiàn)拖拽功能示例

    java swing中實(shí)現(xiàn)拖拽功能示例

    這篇文章主要介紹了java swing中實(shí)現(xiàn)拖拽功能示例,需要的朋友可以參考下
    2014-04-04
  • 解決Java中基于GeoTools的Shapefile讀取亂碼的問題

    解決Java中基于GeoTools的Shapefile讀取亂碼的問題

    本文主要討論了在使用Java編程語言進(jìn)行地理信息數(shù)據(jù)解析時(shí)遇到的Shapefile屬性信息亂碼問題,以及根據(jù)不同的編碼設(shè)置進(jìn)行屬性信息解析的方法,感興趣的朋友跟隨小編一起看看吧
    2025-03-03
  • springboot讀取resources下文件的方式詳解

    springboot讀取resources下文件的方式詳解

    最近寫讀取模板文件做一些后續(xù)的處理,將文件放在了項(xiàng)目的resources下,發(fā)現(xiàn)了一個(gè)好用的讀取方法,下面這篇文章主要給大家介紹了關(guān)于springboot讀取resources下文件的相關(guān)資料,需要的朋友可以參考下
    2022-06-06
  • MyBatis參數(shù)處理實(shí)現(xiàn)方法匯總

    MyBatis參數(shù)處理實(shí)現(xiàn)方法匯總

    這篇文章主要介紹了MyBatis參數(shù)處理實(shí)現(xiàn)方法匯總,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-08-08
  • 使用Java實(shí)現(xiàn)Excel導(dǎo)入并進(jìn)行數(shù)據(jù)校驗(yàn)

    使用Java實(shí)現(xiàn)Excel導(dǎo)入并進(jìn)行數(shù)據(jù)校驗(yàn)

    這篇文章主要為大家詳細(xì)介紹了Java如何根據(jù)下載的指定數(shù)據(jù)模板寫入數(shù)據(jù)和導(dǎo)入并進(jìn)行數(shù)據(jù)校驗(yàn),文中的示例代碼講解詳細(xì),需要的可以了解下
    2025-03-03
  • 為何Java單例模式我只推薦兩種

    為何Java單例模式我只推薦兩種

    這篇文章主要給大家介紹了關(guān)于Java單例模式推薦的兩種模式,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06
  • Spring Boot超詳細(xì)講解請求處理流程機(jī)制

    Spring Boot超詳細(xì)講解請求處理流程機(jī)制

    SpringBoot是一種整合Spring技術(shù)棧的方式(或者說是框架),同時(shí)也是簡化Spring的一種快速開發(fā)的腳手架,本篇讓我們一起分析請求處理流程機(jī)制
    2022-07-07

最新評論

江阴市| 北川| 乌兰察布市| 册亨县| 庆元县| 阳东县| 文水县| 平南县| 永济市| 泉州市| 汉寿县| 武隆县| 白沙| 茂名市| 班玛县| 双柏县| 皮山县| 洛浦县| 溆浦县| 霍林郭勒市| 崇义县| 宜黄县| 扬州市| 海城市| 沙湾县| 平度市| 张北县| 西藏| 夏河县| 织金县| 南昌市| 灵寿县| 渝北区| 襄樊市| 滁州市| 盐池县| 庐江县| 水富县| 淄博市| 花垣县| 阳原县|