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

java同步之如何寫(xiě)一個(gè)鎖Lock

 更新時(shí)間:2019年05月24日 08:41:12   作者:彤哥讀源碼  
本篇文章的目標(biāo)一是自己動(dòng)手寫(xiě)一個(gè)鎖,這個(gè)鎖的功能很簡(jiǎn)單,能進(jìn)行正常的加鎖、解鎖操作。能更好地理解后面章節(jié)將要學(xué)習(xí)的AQS及各種同步器實(shí)現(xiàn)的原理。下面小編來(lái)和大家一起學(xué)習(xí)下吧

問(wèn)題

(1)自己動(dòng)手寫(xiě)一個(gè)鎖需要哪些知識(shí)?

(2)自己動(dòng)手寫(xiě)一個(gè)鎖到底有多簡(jiǎn)單?

(3)自己能不能寫(xiě)出來(lái)一個(gè)完美的鎖?

簡(jiǎn)介

本篇文章的目標(biāo)一是自己動(dòng)手寫(xiě)一個(gè)鎖,這個(gè)鎖的功能很簡(jiǎn)單,能進(jìn)行正常的加鎖、解鎖操作。

本篇文章的目標(biāo)二是通過(guò)自己動(dòng)手寫(xiě)一個(gè)鎖,能更好地理解后面章節(jié)將要學(xué)習(xí)的AQS及各種同步器實(shí)現(xiàn)的原理。

分析

自己動(dòng)手寫(xiě)一個(gè)鎖需要準(zhǔn)備些什么呢?

首先,在上一章學(xué)習(xí)synchronized的時(shí)候我們說(shuō)過(guò)它的實(shí)現(xiàn)原理是更改對(duì)象頭中的MarkWord,標(biāo)記為已加鎖或未加鎖。

但是,我們自己是無(wú)法修改對(duì)象頭信息的,那么我們可不可以用一個(gè)變量來(lái)代替呢?

比如,這個(gè)變量的值為1的時(shí)候就說(shuō)明已加鎖,變量值為0的時(shí)候就說(shuō)明未加鎖,我覺(jué)得可行。

其次,我們要保證多個(gè)線程對(duì)上面我們定義的變量的爭(zhēng)用是可控的,所謂可控即同時(shí)只能有一個(gè)線程把它的值修改為1,且當(dāng)它的值為1的時(shí)候其它線程不能再修改它的值,這種是不是就是典型的CAS操作,所以我們需要使用Unsafe這個(gè)類來(lái)做CAS操作。

然后,我們知道在多線程的環(huán)境下,多個(gè)線程對(duì)同一個(gè)鎖的爭(zhēng)用肯定只有一個(gè)能成功,那么,其它的線程就要排隊(duì),所以我們還需要一個(gè)隊(duì)列。

最后,這些線程排隊(duì)的時(shí)候干嘛呢?它們不能再繼續(xù)執(zhí)行自己的程序,那就只能阻塞了,阻塞完了當(dāng)輪到這個(gè)線程的時(shí)候還要喚醒,所以我們還需要Unsfae這個(gè)類來(lái)阻塞(park)和喚醒(unpark)線程。

基于以上四點(diǎn),我們需要的神器大致有:一個(gè)變量、一個(gè)隊(duì)列、執(zhí)行CAS/park/unpark的Unsafe類。

大概的流程圖如下圖所示:

關(guān)于Unsafe類的相關(guān)講解請(qǐng)參考之前發(fā)的文章:

java Unsafe詳細(xì)解析

解決

一個(gè)變量

這個(gè)變量只支持同時(shí)只有一個(gè)線程能把它修改為1,所以它修改完了一定要讓其它線程可見(jiàn),因此,這個(gè)變量需要使用volatile來(lái)修飾。

private volatile int state;

CAS

這個(gè)變量的修改必須是原子操作,所以我們需要CAS更新它,我們這里使用Unsafe來(lái)直接CAS更新int類型的state。

當(dāng)然,這個(gè)變量如果直接使用AtomicInteger也是可以的,不過(guò),既然我們學(xué)習(xí)了更底層的Unsafe類那就應(yīng)該用(浪)起來(lái)。

private boolean compareAndSetState(int expect, int update) {
 return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

一個(gè)隊(duì)列

隊(duì)列的實(shí)現(xiàn)有很多,數(shù)組、鏈表都可以,我們這里采用鏈表,畢竟鏈表實(shí)現(xiàn)隊(duì)列相對(duì)簡(jiǎn)單一些,不用考慮擴(kuò)容等問(wèn)題。

這個(gè)隊(duì)列的操作很有特點(diǎn):

放元素的時(shí)候都是放到尾部,且可能是多個(gè)線程一起放,所以對(duì)尾部的操作要CAS更新;

喚醒一個(gè)元素的時(shí)候從頭部開(kāi)始,但同時(shí)只有一個(gè)線程在操作,即獲得了鎖的那個(gè)線程,所以對(duì)頭部的操作不需要CAS去更新。

private static class Node {
 // 存儲(chǔ)的元素為線程
 Thread thread;
 // 前一個(gè)節(jié)點(diǎn)(可以沒(méi)有,但實(shí)現(xiàn)起來(lái)很困難)
 Node prev;
 // 后一個(gè)節(jié)點(diǎn)
 Node next;

 public Node() {
 }

 public Node(Thread thread, Node prev) {
 this.thread = thread;
 this.prev = prev;
 }
}
// 鏈表頭
private volatile Node head;
// 鏈表尾
private volatile Node tail;
// 原子更新tail字段
private boolean compareAndSetTail(Node expect, Node update) {
 return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}

這個(gè)隊(duì)列很簡(jiǎn)單,存儲(chǔ)的元素是線程,需要有指向下一個(gè)待喚醒的節(jié)點(diǎn),前一個(gè)節(jié)點(diǎn)可有可無(wú),但是沒(méi)有實(shí)現(xiàn)起來(lái)很困難,不信學(xué)完這篇文章你試試。

加鎖

public void lock() {
 // 嘗試更新state字段,更新成功說(shuō)明占有了鎖
 if (compareAndSetState(0, 1)) {
 return;
 }
 // 未更新成功則入隊(duì)
 Node node = enqueue();
 Node prev = node.prev;
 // 再次嘗試獲取鎖,需要檢測(cè)上一個(gè)節(jié)點(diǎn)是不是head,按入隊(duì)順序加鎖
 while (node.prev != head || !compareAndSetState(0, 1)) {
 // 未獲取到鎖,阻塞
 unsafe.park(false, 0L);
 }
 // 下面不需要原子更新,因?yàn)橥瑫r(shí)只有一個(gè)線程訪問(wèn)到這里
 // 獲取到鎖了且上一個(gè)節(jié)點(diǎn)是head
 // head后移一位
 head = node;
 // 清空當(dāng)前節(jié)點(diǎn)的內(nèi)容,協(xié)助GC
 node.thread = null;
 // 將上一個(gè)節(jié)點(diǎn)從鏈表中剔除,協(xié)助GC
 node.prev = null;
 prev.next = null;
}
// 入隊(duì)
private Node enqueue() {
 while (true) {
 // 獲取尾節(jié)點(diǎn)
 Node t = tail;
 // 構(gòu)造新節(jié)點(diǎn)
 Node node = new Node(Thread.currentThread(), t);
 // 不斷嘗試原子更新尾節(jié)點(diǎn)
 if (compareAndSetTail(t, node)) {
 // 更新尾節(jié)點(diǎn)成功了,讓原尾節(jié)點(diǎn)的next指針指向當(dāng)前節(jié)點(diǎn)
 t.next = node;
 return node;
 }
 }
}

(1)嘗試獲取鎖,成功了就直接返回;

(2)未獲取到鎖,就進(jìn)入隊(duì)列排隊(duì);

(3)入隊(duì)之后,再次嘗試獲取鎖;

(4)如果不成功,就阻塞;

(5)如果成功了,就把頭節(jié)點(diǎn)后移一位,并清空當(dāng)前節(jié)點(diǎn)的內(nèi)容,且與上一個(gè)節(jié)點(diǎn)斷絕關(guān)系;

(6)加鎖結(jié)束;

解鎖

// 解鎖
public void unlock() {
 // 把state更新成0,這里不需要原子更新,因?yàn)橥瑫r(shí)只有一個(gè)線程訪問(wèn)到這里
 state = 0;
 // 下一個(gè)待喚醒的節(jié)點(diǎn)
 Node next = head.next;
 // 下一個(gè)節(jié)點(diǎn)不為空,就喚醒它
 if (next != null) {
 unsafe.unpark(next.thread);
 }
}

(1)把state改成0,這里不需要CAS更新,因?yàn)楝F(xiàn)在還在加鎖中,只有一個(gè)線程去更新,在這句之后就釋放了鎖;

(2)如果有下一個(gè)節(jié)點(diǎn)就喚醒它;

(3)喚醒之后就會(huì)接著走上面lock()方法的while循環(huán)再去嘗試獲取鎖;

(4)喚醒的線程不是百分之百能獲取到鎖的,因?yàn)檫@里state更新成0的時(shí)候就解鎖了,之后可能就有線程去嘗試加鎖了。

測(cè)試

上面完整的鎖的實(shí)現(xiàn)就完了,是不是很簡(jiǎn)單,但是它是不是真的可靠呢,敢不敢來(lái)試試?!

直接上測(cè)試代碼:

private static int count = 0;

public static void main(String[] args) throws InterruptedException {
 MyLock lock = new MyLock();

 CountDownLatch countDownLatch = new CountDownLatch(1000);

 IntStream.range(0, 1000).forEach(i -> new Thread(() -> {
 lock.lock();

 try {
 IntStream.range(0, 10000).forEach(j -> {
 count++;
 });
 } finally {
 lock.unlock();
 }
// System.out.println(Thread.currentThread().getName());
 countDownLatch.countDown();
 }, "tt-" + i).start());

 countDownLatch.await();

 System.out.println(count);
}

運(yùn)行這段代碼的結(jié)果是總是打印出10000000(一千萬(wàn)),說(shuō)明我們的鎖是正確的、可靠的、完美的。

總結(jié)

(1)自己動(dòng)手寫(xiě)一個(gè)鎖需要做準(zhǔn)備:一個(gè)變量、一個(gè)隊(duì)列、Unsafe類。

(2)原子更新變量為1說(shuō)明獲得鎖成功;

(3)原子更新變量為1失敗說(shuō)明獲得鎖失敗,進(jìn)入隊(duì)列排隊(duì);

(4)更新隊(duì)列尾節(jié)點(diǎn)的時(shí)候是多線程競(jìng)爭(zhēng)的,所以要使用原子更新;

(5)更新隊(duì)列頭節(jié)點(diǎn)的時(shí)候只有一個(gè)線程,不存在競(jìng)爭(zhēng),所以不需要使用原子更新;

(6)隊(duì)列節(jié)點(diǎn)中的前一個(gè)節(jié)點(diǎn)prev的使用很巧妙,沒(méi)有它將很難實(shí)現(xiàn)一個(gè)鎖,只有寫(xiě)過(guò)的人才明白,不信你試試^^

彩蛋

(1)我們實(shí)現(xiàn)的鎖支持可重入嗎?

答:不可重入,因?yàn)槲覀兠看沃话裺tate更新為1。如果要支持可重入也很簡(jiǎn)單,獲取鎖時(shí)檢測(cè)鎖是不是被當(dāng)前線程占有著,如果是就把state的值加1,釋放鎖時(shí)每次減1即可,減為0時(shí)表示鎖已釋放。

(2)我們實(shí)現(xiàn)的鎖是公平鎖還是非公平鎖?

答:非公平鎖,因?yàn)楂@取鎖的時(shí)候我們先嘗試了一次,這里并不是嚴(yán)格的排隊(duì),所以是非公平鎖。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Struts2學(xué)習(xí)筆記(1)-入門(mén)教程

    Struts2學(xué)習(xí)筆記(1)-入門(mén)教程

    本文是一個(gè)Struts2的簡(jiǎn)單入門(mén)教程,比較簡(jiǎn)單,希望能給大家做一個(gè)參考。
    2016-06-06
  • Springboot如何使用OSHI獲取和操作系統(tǒng)和硬件信息

    Springboot如何使用OSHI獲取和操作系統(tǒng)和硬件信息

    這篇文章主要介紹了Springboot如何使用OSHI獲取和操作系統(tǒng)和硬件信息問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • springboot使用@KafkaListener監(jiān)聽(tīng)多個(gè)kafka配置實(shí)現(xiàn)

    springboot使用@KafkaListener監(jiān)聽(tīng)多個(gè)kafka配置實(shí)現(xiàn)

    當(dāng)服務(wù)中需要監(jiān)聽(tīng)多個(gè)kafka時(shí),?需要配置多個(gè)kafka,本文主要介紹了springboot使用@KafkaListener監(jiān)聽(tīng)多個(gè)kafka配置實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-04-04
  • Java設(shè)計(jì)模式之工廠方法和抽象工廠

    Java設(shè)計(jì)模式之工廠方法和抽象工廠

    本文詳細(xì)講解了Java設(shè)計(jì)模式之工廠方法和抽象工廠,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-09-09
  • 關(guān)于Java中反射的作用義及使用方法

    關(guān)于Java中反射的作用義及使用方法

    這篇文章主要介紹了關(guān)于Java中反射的作用義及使用方法,反射使得程序可以在運(yùn)行時(shí)對(duì)類進(jìn)行檢查和操作,而不需要在編譯時(shí)知道類的完整信息,需要的朋友可以參考下
    2023-07-07
  • Java優(yōu)雅的處理金錢問(wèn)題(BigDecimal)

    Java優(yōu)雅的處理金錢問(wèn)題(BigDecimal)

    本文主要介紹了Java優(yōu)雅的處理金錢問(wèn)題(BigDecimal),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • JAVA編程實(shí)現(xiàn)TCP網(wǎng)絡(luò)通訊的方法示例

    JAVA編程實(shí)現(xiàn)TCP網(wǎng)絡(luò)通訊的方法示例

    這篇文章主要介紹了JAVA編程實(shí)現(xiàn)TCP網(wǎng)絡(luò)通訊的方法,簡(jiǎn)單說(shuō)明了TCP通訊的原理并結(jié)合具體實(shí)例形式分析了java實(shí)現(xiàn)TCP通訊的步驟與相關(guān)操作技巧,需要的朋友可以參考下
    2017-08-08
  • SpringBoot2整合Redis實(shí)現(xiàn)讀寫(xiě)操作

    SpringBoot2整合Redis實(shí)現(xiàn)讀寫(xiě)操作

    Redis,對(duì)于大家來(lái)說(shuō)應(yīng)該不陌生,是經(jīng)常使用的開(kāi)發(fā)技術(shù)之一。本文將結(jié)合實(shí)例代碼,介紹SpringBoot2整合Redis實(shí)現(xiàn)讀寫(xiě)操作,感興趣的小伙伴們可以參考一下
    2021-07-07
  • mybatis 延遲加載的深入理解

    mybatis 延遲加載的深入理解

    這篇文章主要介紹了mybatis 延遲加載的深入理解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-01-01
  • springboot websocket集群(stomp協(xié)議)連接時(shí)候傳遞參數(shù)

    springboot websocket集群(stomp協(xié)議)連接時(shí)候傳遞參數(shù)

    這篇文章主要介紹了springboot websocket集群(stomp協(xié)議)連接時(shí)候傳遞參數(shù),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07

最新評(píng)論

黎平县| 娄底市| 都匀市| 南丹县| 武威市| 黎城县| 西昌市| 漳平市| 新营市| 仙游县| 科尔| 吉水县| 长岛县| 阳谷县| 池州市| 永和县| 吐鲁番市| 大荔县| 德保县| 汶上县| 夏邑县| 苏州市| 体育| 宁安市| 河池市| 盐津县| 朝阳县| 平江县| 乐清市| 德昌县| 千阳县| 定兴县| 盐边县| 康定县| 专栏| 临猗县| 武胜县| 房山区| 青铜峡市| 定州市| 健康|