[03][01][03] 線程安全性的原理分析

[TOC]

初步認(rèn)識 Volatile

一段代碼引發(fā)的思考

下面這段代碼睁蕾,演示了一個使用 volatile 以及沒使用 volatile 這個關(guān)鍵字,對于變量更新的影響

public class VolatileDemo {

    public /*volatile*/ static boolean stop = false;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            int i = 0;

            //condition 不滿足
            while (!stop) {
                I++;
            }
        });
        t1.start();

        Thread.sleep(1000);

        stop = true;
    }
}

當(dāng) stop 變量的 volatile 被注釋時稠腊,主線程修改 stop 的值為 true,子線程無法感知導(dǎo)致 while (!stop) 一直運行

volatile 的作用

volatile 可以使得在多處理器環(huán)境下保證了共享變量的可見性鸣哀,那么到底什么是可見性呢架忌?不知道大家有沒有思考過這個問題

在單線程的環(huán)境下,如果向一個變量先寫入一個值我衬,然后在沒有寫干涉的情況下讀取這個變量的值叹放,那這個時候讀取到的這個變量的值應(yīng)該是之前寫入的那個值。這本來是一個很正常的事情低飒。但是在多線程環(huán)境下许昨,讀和寫發(fā)生在不同的線程中的時候,可能會出現(xiàn):讀線程不能及時的讀取到其他線程寫入的最新的值褥赊。這就是所謂的可見性為了實現(xiàn)跨線程寫入的內(nèi)存可見性糕档,必須使用到一些機制來實現(xiàn)。而 volatile 就是這樣一種機制

volatile 關(guān)鍵字是如何保證可見性的拌喉?

我們可以使用 hsdis 這個工具速那,來查看前面演示的這段代碼的匯編指令,具體的使用請查看使用說明文檔

在運行的代碼中尿背,設(shè)置 jvm 參數(shù)如下
XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly

hsdis安裝以及使用

然后在輸出的結(jié)果中端仰,查找下 lock 指令,發(fā)現(xiàn)在修改帶有 volatile 修飾的成員變量時田藐,會多一個 lock 指令荔烧。lock是一種控制指令吱七,在多處理器環(huán)境下,lock 匯編指令可以基于總線鎖或者緩存鎖的機制來達到可見性的一個效果

為了讓大家更好的理解可見性的本質(zhì)鹤竭,我們需要從硬件層面進行梳理

從硬件層面了解可見性的本質(zhì)

一臺計算機中最核心的組件是 CPU踊餐、內(nèi)存、以及 I/O 設(shè)備臀稚。在整個計算機的發(fā)展歷程中吝岭,除了 CPU、內(nèi)存以及 I/O 設(shè)備不斷迭代升級來提升計算機處理性能之外吧寺,還有一個非常核心的矛盾點窜管,就是這三者在處理速度的差異。CPU 的計算速度是非持苫快的幕帆,內(nèi)存次之、最后是 IO 設(shè)備比如磁盤抒钱。而在絕大部分的程序中蜓肆,一定會存在內(nèi)存訪問,有些可能還會存在 I/O 設(shè)備的訪問

為了提升計算性能谋币,CPU 從單核升級到了多核甚至用到了超線程技術(shù)最大化提高 CPU 的處理性能仗扬,但是僅僅提升CPU 性能還不夠,如果后面兩者的處理性能沒有跟上蕾额,意味著整體的計算效率取決于最慢的設(shè)備早芭。為了平衡三者的速度差異,最大化的利用 CPU 提升性能诅蝶,從硬件退个、操作系統(tǒng)、編譯器等方面都做出了很多的優(yōu)化

  • CPU 增加了高速緩存
  • 操作系統(tǒng)增加了進程调炬、線程语盈。通過 CPU 的時間片切換最大化的提升 CPU 的使用率
  • 編譯器的指令優(yōu)化,更合理的去利用好 CPU 的高速緩存

然后每一種優(yōu)化缰泡,都會帶來相應(yīng)的問題刀荒,而這些問題也是導(dǎo)致線程安全性問題的根源。為了了解前面提到的可見性問題的本質(zhì)棘钞,我們有必要去了解這些優(yōu)化的過程

CPU 高速緩存

線程是 CPU 調(diào)度的最小單元缠借,線程設(shè)計的目的最終仍然是更充分的利用計算機處理的效能,但是絕大部分的運算任務(wù)不能只依靠處理器“計算”就能完成宜猜,處理器還需要與內(nèi)存交互泼返,比如讀取運算數(shù)據(jù)、存儲運算結(jié)果姨拥,這個 I/O 操作是很難消除的绅喉。而由于計算機的存儲設(shè)備與處理器的運算速度差距非常大渠鸽,所以現(xiàn)代計算機系統(tǒng)都會增加一層讀寫速度盡可能接近處理器運算速度的高速緩存來作為內(nèi)存和處理器之間的緩沖:將運算需要使用的數(shù)據(jù)復(fù)制到緩存中,讓運算能快速進行霹疫,當(dāng)運算結(jié)束后再從緩存同步到內(nèi)存之中

CPU 高速緩存

通過高速緩存的存儲交互很好的解決了處理器與內(nèi)存的速度矛盾拱绑,但是也為計算機系統(tǒng)帶來了更高的復(fù)雜度,因為它引入了一個新的問題丽蝎,緩存一致性

什么叫緩存一致性呢

首先,有了高速緩存的存在以后膀藐,每個 CPU 的處理過程是屠阻,先將計算需要用到的數(shù)據(jù)緩存在 CPU 高速緩存中,在 CPU 進行計算時额各,直接從高速緩存中讀取數(shù)據(jù)并且在計算完成之后寫入到緩存中国觉。在整個運算過程完成后,再把緩存中的數(shù)據(jù)同步到主內(nèi)存

由于在多 CPU 種虾啦,每個線程可能會運行在不同的 CPU 內(nèi)麻诀,并且每個線程擁有自己的高速緩存。同一份數(shù)據(jù)可能會被緩存到多個 CPU 中傲醉,如果在不同 CPU 中運行的不同線程看到同一份內(nèi)存的緩存值不一樣就會存在緩存不一致的問題

為了解決緩存不一致的問題蝇闭,在 CPU 層面做了很多事情,主要提供了兩種解決辦法

  • 總線鎖
  • 緩存鎖

總線鎖和緩存鎖

總線鎖硬毕,簡單來說就是呻引,在多 cpu 下,當(dāng)其中一個處理器要對共享內(nèi)存進行操作的時候吐咳,在總線上發(fā)出一個 LOCK# 信號逻悠,這個信號使得其他處理器無法通過總線來訪問到共享內(nèi)存中的數(shù)據(jù),總線鎖定把 CPU 和內(nèi)存之間的通信鎖住了韭脊,這使得鎖定期間童谒,其他處理器不能操作其他內(nèi)存地址的數(shù)據(jù),所以總線鎖定的開銷比較大沪羔,這種機制顯然是不合適的

如何優(yōu)化呢饥伊?最好的方法就是控制鎖的保護粒度,我們只需要保證對于被多個 CPU 緩存的同一份數(shù)據(jù)是一致的就行任内。所以引入了緩存鎖撵渡,它核心機制是基于緩存一致性協(xié)議來實現(xiàn)的

緩存一致性協(xié)議

為了達到數(shù)據(jù)訪問的一致,需要各個處理器在訪問緩存時遵循一些協(xié)議死嗦,在讀寫時根據(jù)協(xié)議來操作趋距,常見的協(xié)議有 MSI,MESI越除,MOSI 等节腐。最常見的就是 MESI 協(xié)議外盯,MESI 表示緩存行的四種狀態(tài),分別是

  • M(Modify) 表示共享數(shù)據(jù)只緩存在當(dāng)前 CPU 緩存中翼雀,并且是被修改狀態(tài)饱苟,也就是緩存的數(shù)據(jù)和主內(nèi)存中的數(shù)據(jù)不一致
  • E(Exclusive) 表示緩存的獨占狀態(tài),數(shù)據(jù)只緩存在當(dāng)前CPU 緩存中狼渊,并且沒有被修改
  • S(Shared) 表示數(shù)據(jù)可能被多個 CPU 緩存箱熬,并且各個緩存中的數(shù)據(jù)和主內(nèi)存數(shù)據(jù)一致
  • I(Invalid) 表示緩存已經(jīng)失效在 MESI 協(xié)議中,每個緩存的緩存控制器不僅知道自己的讀寫操作狈邑,而且也監(jiān)聽 (snoop) 其它 Cache 的讀寫操作
image

image

image

對于 MESI 協(xié)議城须,從 CPU 讀寫角度來說會遵循以下原則:

  • CPU 讀請求:緩存處于 M、E米苹、S 狀態(tài)都可以被讀取糕伐,I 狀 態(tài) CPU 只能從主存中讀取數(shù)據(jù)
  • CPU 寫請求:緩存處于 M、E 狀態(tài)才可以被寫蘸嘶。對于 S 狀態(tài)的寫良瞧,需要將其他 CPU 中緩存行置為無效才可寫

使用總線鎖和緩存鎖機制之后,CPU 對于內(nèi)存的操作大概可以抽象成下面這樣的結(jié)構(gòu)训唱。從而達到緩存一致性效果


image

總結(jié)可見性的本質(zhì)

由于 CPU 高速緩存的出現(xiàn)使得 如果多個 cpu 同時緩存了相同的共享數(shù)據(jù)時褥蚯,可能存在可見性問題。也就是 CPU0 修改了自己本地緩存的值對于 CPU1 不可見雪情。不可見導(dǎo)致的后果是 CPU1 后續(xù)在對該數(shù)據(jù)進行寫入操作時遵岩,是使用的臟數(shù)據(jù)。使得數(shù)據(jù)最終的結(jié)果不可預(yù)測

很多同學(xué)肯定希望想在代碼里面去模擬一下可見性的問題巡通,實際上尘执,這種情況很難模擬。因為我們無法讓某個線程指定某個特定 CPU宴凉,這是系統(tǒng)底層的算法誊锭, JVM 應(yīng)該也是沒法控制的。還有最重要的一點弥锄,就是你無法預(yù)測 CPU 緩 存什么時候會把值傳給主存丧靡,可能這個時間間隔非常短,短到你無法觀察到籽暇。最后就是線程的執(zhí)行的順序問題温治,因為多線程你無法控制哪個線程的某句代碼會在另一個線程的某句代碼后面馬上執(zhí)行

所以我們只能基于它的原理去了解這樣一個存在的客觀事實

了解到這里,大家應(yīng)該會有一個疑問戒悠,剛剛不是說基于緩存一致性協(xié)議或者總線鎖能夠達到緩存一致性的要求嗎熬荆?為什么還需要加 volatile 關(guān)鍵字?或者說為什么還會存在可見性問題呢绸狐?

MESI 優(yōu)化帶來的可見性問題

MESI 協(xié)議雖然可以實現(xiàn)緩存的一致性卤恳,但是也會存在一些問題

就是各個 CPU 緩存行的狀態(tài)是通過消息傳遞來進行的累盗。如 果 CPU0 要對一個在緩存中共享的變量進行寫入,首先需要發(fā)送一個失效的消息給到其他緩存了該數(shù)據(jù)的 CPU突琳。并且要等到他們的確認(rèn)回執(zhí)若债。CPU0 在這段時間內(nèi)都會處于阻塞狀態(tài)。為了避免阻塞帶來的資源浪費拆融。在 cpu 中引入了 Store Bufferes

image

CPU0 只需要在寫入共享數(shù)據(jù)時蠢琳,直接把數(shù)據(jù)寫入到 store bufferes 中,同時發(fā)送 invalidate 消息镜豹,然后繼續(xù)去處理其他指令

當(dāng)收到其他所有 CPU 發(fā)送了 invalidate acknowledge 消息時挪凑,再將 store bufferes 中的數(shù)據(jù)數(shù)據(jù)存儲至 cache line中。最后再從緩存行同步到主內(nèi)存

image

但是這種優(yōu)化存在兩個問題

  • 數(shù)據(jù)什么時候提交是不確定的逛艰,因為需要等待其他 cpu 給回復(fù)才會進行數(shù)據(jù)同步。這里其實是一個異步操作
  • 引入了 store bufferes 后搞旭,處理器會先嘗試從 store buffer 中讀取值散怖,如果 store buffer 中有數(shù)據(jù),則直接從 store buffer 中讀取肄渗,否則就再從緩存行中讀取

我們來看一個例子


image

exeToCPU0 和 exeToCPU1 分別在兩個獨立的 CPU 上執(zhí)行镇眷。假如 CPU0 的緩存行中緩存了 isFinish 這個共享變量,并且狀態(tài)為(E)翎嫡、而 Value 可能是(S)狀態(tài)

那么這個時候欠动,CPU0 在執(zhí)行的時候,會先把 value=10 的指令寫入到 store buffer 中惑申。并且通知給其他緩存了該 value 變量的 CPU具伍。在等待其他 CPU 通知結(jié)果的時候,CPU0 會繼續(xù)執(zhí)行 isFinish=true 這個指令

而因為當(dāng)前 CPU0 緩存了 isFinish 并且是 Exclusive 狀態(tài),所以可以直接修改 isFinish=true圈驼。這個時候 CPU1 發(fā)起 read 操作去讀取 isFinish 的值可能為 true人芽,但是 value 的值不等于 10

這種情況我們可以認(rèn)為是 CPU 的亂序執(zhí)行,也可以認(rèn)為是一種重排序绩脆,而這種重排序會帶來可見性的問題

這下硬件工程師也抓狂了萤厅,我們也能理解,從硬件層面很難去知道軟件層面上的這種前后依賴關(guān)系靴迫,所以沒有辦法通過某種手段自動去解決

所以硬件工程師就說: 既然怎么優(yōu)化都不符合你的要求惕味,要不你來寫吧

所以在 CPU 層面提供了 memory barrier(內(nèi)存屏障)的指令,從硬件層面來看這個 memroy barrier 就是 CPU flush store bufferes 中的指令玉锌。軟件層面可以決定在適當(dāng)?shù)牡胤絹聿迦雰?nèi)存屏障

CPU 層面的內(nèi)存屏障

什么是內(nèi)存屏障?從前面的內(nèi)容基本能有一個初步的猜想名挥,內(nèi)存屏障就是將 store bufferes 中的指令寫入到內(nèi)存,從而使得其他訪問同一共享內(nèi)存的線程的可見性

X86 的 memory barrier 指令包括 lfence(讀屏障) sfence(寫屏障) mfence(全屏障)

  • Store Memory Barrier(寫屏障) 告訴處理器在寫屏障之前的所有已經(jīng)存儲在存儲緩存(store bufferes)中的數(shù)據(jù)同步到主內(nèi)存芬沉,簡單來說就是使得寫屏障之前的指令的結(jié)果對屏障之后的讀或者寫是可見的
  • Load Memory Barrier(讀屏障) 處理器在讀屏障之后的讀操作,都在讀屏障之后執(zhí)行躺同。配合寫屏障阁猜,使得寫屏障之前的內(nèi)存更新對于讀屏障之后的讀操作是可見的
  • Full Memory Barrier(全屏障) 確保屏障前的內(nèi)存讀寫操作的結(jié)果提交到內(nèi)存之后,再執(zhí)行屏障后的讀寫操作

有了內(nèi)存屏障以后蹋艺,對于上面這個例子剃袍,我們可以這么來改,從而避免出現(xiàn)可見性問題


內(nèi)存屏障

總的來說捎谨,內(nèi)存屏障的作用可以通過防止 CPU 對內(nèi)存的亂序訪問來保證共享數(shù)據(jù)在多線程并行執(zhí)行下的可見性

但是這個屏障怎么來加呢民效?回到最開始我們講 volatile 關(guān)鍵字的代碼,這個關(guān)鍵字會生成一個 Lock 的匯編指令涛救,這個指令其實就相當(dāng)于實現(xiàn)了一種內(nèi)存屏障

這個時候問題又來了畏邢,內(nèi)存屏障、重排序這些東西好像是和平臺以及硬件架構(gòu)有關(guān)系的检吆。作為 Java 語言的特性舒萎,一次編寫多處運行。我們不應(yīng)該考慮平臺相關(guān)的問題蹭沛,并且這些所謂的內(nèi)存屏障也不應(yīng)該讓程序員來關(guān)心

JMM

什么是 JMM

JMM 全稱是 Java Memory Model

通過前面的分析發(fā)現(xiàn)臂寝,導(dǎo)致可見性問題的根本原因是緩存以及重排序。而 JMM 實際上就是提供了合理的禁用緩存以及禁止重排序的方法摊灭。所以它最核心的價值在于解決可見性和有序性

JMM 屬于語言級別的抽象內(nèi)存模型咆贬,可以簡單理解為對硬件模型的抽象,它定義了共享內(nèi)存中多線程程序讀寫操作的行為規(guī)范:在虛擬機中把共享變量存儲到內(nèi)存以及從內(nèi)存中取出共享變量的底層實現(xiàn)細節(jié)

通過這些規(guī)則來規(guī)范對內(nèi)存的讀寫操作從而保證指令的正確性帚呼,它解決了 CPU 多級緩存掏缎、處理器優(yōu)化、指令重排序?qū)е碌膬?nèi)存訪問問題煤杀,保證了并發(fā)場景下的可見性眷蜈。需要注意的是 JMM 并沒有限制執(zhí)行引擎使用處理器的寄存器或者高速緩存來提升指令執(zhí)行速度,也沒有限制編譯器對指令進行重排序怜珍,也就是說在 JMM 中端蛆,也會存在緩存一致性問題和指令重排序問題。只是 JMM 把底層的問題抽象到 JVM 層面酥泛,再基于 CPU 層面提供的內(nèi)存屏障指令今豆,以及限制編譯器的重排序來解決并發(fā)問題

JMM 抽象模型分為主內(nèi)存、工作內(nèi)存柔袁;主內(nèi)存是所有線程共享的呆躲,一般是實例對象、靜態(tài)字段捶索、數(shù)組對象等存儲在堆內(nèi)存中的變量插掂。工作內(nèi)存是每個線程獨占的,線程對變量的所有操作都必須在工作內(nèi)存中進行,不能直接讀寫主內(nèi)存中的變量辅甥,線程之間的共享變量值的傳遞都是基于主內(nèi)存來完成

Java 內(nèi)存模型底層實現(xiàn)可以簡單的認(rèn)為:通過內(nèi)存屏障 (memory barrier) 禁止重排序酝润,即時編譯器根據(jù)具體的底層體系架構(gòu),將這些內(nèi)存屏障替換成具體的 CPU 指令璃弄。對于編譯器而言要销,內(nèi)存屏障將限制它所能做的重排序優(yōu)化。而對于處理器而言夏块,內(nèi)存屏障將會導(dǎo)致緩存的刷新操作疏咐。比如,對于 volatile脐供,編譯器將在 volatile 字段的讀寫操作前后各插入一些內(nèi)存屏障

JMM 是如何解決可見性有序性問題的

簡單來說浑塞,JMM 提供了一些禁用緩存以及進制重排序的方法,來解決可見性和有序性問題政己。這些方法大家都很熟悉:volatile酌壕、synchronized、final

JMM 如何解決順序一致性問題

重排序問題

為了提高程序的執(zhí)行性能歇由,編譯器和處理器都會對指令做重排序仅孩,其中處理器的重排序在前面已經(jīng)分析過了。所謂的重排序其實就是指執(zhí)行的指令順序

編譯器的重排序指的是程序編寫的指令在編譯之后印蓖,指令可能會產(chǎn)生重排序來優(yōu)化程序的執(zhí)行性能

從源代碼到最終執(zhí)行的指令,可能會經(jīng)過三種重排序


重排序問題

2 和 3 屬于處理器重排序京腥。這些重排序可能會導(dǎo)致可見性問題

編譯器的重排序赦肃,JMM 提供了禁止特定類型的編譯器重排序

處理器重排序,JMM 會要求編譯器生成指令時公浪,會插入內(nèi)存屏障來禁止處理器重排序

當(dāng)然并不是所有的程序都會出現(xiàn)重排序問題

編譯器的重排序和 CPU 的重排序的原則一樣他宛,會遵守數(shù)據(jù)依賴性原則,編譯器和處理器不會改變存在數(shù)據(jù)依賴關(guān)系的兩個操作的執(zhí)行順序,比如下面的代碼

場景 1
a = 1; b = a; 
場景 2
a = 1; a = 2;
場景 3
a = b; b = 1;

這三種情況在單線程里面如果改變代碼的執(zhí)行順序欠气,都會導(dǎo)致結(jié)果不一致厅各,所以重排序不會對這類的指令做優(yōu)化。這種規(guī)則也成為 as-if-serial预柒。不管怎么重排序队塘,對于單個線程來說執(zhí)行結(jié)果不能改變。比如

// 1
int a = 2;
// 2
int b = 3;
// 3
int rs = a * b;

1 和 3宜鸯、2 和 3 存在數(shù)據(jù)依賴憔古,所以在最終執(zhí)行的指令中,3 不能重排序到 1 和 2 之前淋袖,否則程序會報錯鸿市。由于 1 和 2 不存在數(shù)據(jù)依賴,所以可以重新排列 1 和 2 的順序

JMM 層面的內(nèi)存屏障

為了保證內(nèi)存可見性,Java 編譯器在生成指令序列的適當(dāng)位置會插入內(nèi)存屏障來禁止特定類型的處理器的重排序焰情,在 JMM 中把內(nèi)存屏障分為四類


image

Happen-Before

它的意思表示的是前一個操作的結(jié)果對于后續(xù)操作是可見的陌凳,所以它是一種表達多個線程之間對于內(nèi)存的可見性

所以我們可以認(rèn)為在 JMM 中,如果一個操作執(zhí)行的結(jié)果需要對另一個操作課件内舟,那么這兩個操作必須要存在 happens-before 關(guān)系合敦。這兩個操作可以是同一個線程,也可以是不同的線程

JMM 中有哪些方法建立 happen-before 規(guī)則

程序順序規(guī)則

一個線程中的每個操作谒获,happens-before 于該線程中的任意后續(xù)操作蛤肌,可以簡單認(rèn)為是 as-if-serial。單個線程中的代碼順序不管怎么變批狱,對于結(jié)果來說是不變的順序規(guī)則表示 1 happenns-before 2; 3 happens-before 4

image

volatile 變量規(guī)則

對于 volatile 修飾的變量的寫的操作裸准,一定 happen-before 后續(xù)對于 volatile 變量的讀操作;根據(jù) volatile 規(guī)則赔硫,2 happens before 3

image

傳遞性規(guī)則

如果 1 happens-before 2; 3happens-before 4; 那么傳遞性規(guī)則表示: 1 happens-before 4;

image

start 規(guī)則

如果線程 A 執(zhí)行操作 ThreadB.start(),那么線程 A 的 ThreadB.start() 操作 happens-before 線程 B 中的任意操作

public class StartRule {

    static int x = 0;

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            // 主線程調(diào)用 t1.start() 之前
            // 所有對共享變量的修改炒俱,此處皆可見
            // 此例中,x==10
            // use x=10
        });
        // 此處對共享變量 x 修改
        x = 10;
        t1.start();
    }
}

join 規(guī)則

如果線程 A 執(zhí)行操作 ThreadB.join() 并成功返回爪膊,那么線程 B 中的任意操作 happens-before 于線程 A 從 ThreadB.join() 操作成功返回

public class JoinRule {

    static int x = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("t1");
            //執(zhí)行的結(jié)果對于主線程可見
        });
        Thread t2 = new Thread(() -> {
            System.out.println("t2");
        });
        Thread t3 = new Thread(() -> {
            System.out.println("t3");
        });

        t1.start();
        //阻塞主線程 wait/notify
        t1.join();

        //等到阻塞釋放
        //獲取到t1線程的執(zhí)行結(jié)果.
        t2.start();
        // 建立一個happens-bebefore規(guī)則
        t2.join();

        t3.start();
    }
}

監(jiān)視器鎖的規(guī)則

對一個鎖的解鎖权悟,happens-before 于隨后對這個鎖的加鎖

synchronized (this) {
    // 此處自動加鎖
    // x 是共享變量, 初始值 =10
    if (this.x < 12) {
        this.x = 12; 
    } 
} 
// 此處自動解鎖

假設(shè) x 的初始值是 10,線程 A 執(zhí)行完代碼塊后 x 的值會變成 12(執(zhí)行完自動釋放鎖)推盛,線程 B 進入代碼塊時峦阁,能夠看到線程 A 對 x 的寫操作,也就是線程 B 能夠看到 x==12

資源信息

本文設(shè)計的代碼可前往下載:https://github.com/huaweirookie/toalibaba/tree/master/concurrent-programming/thread-basic

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末耘成,一起剝皮案震驚了整個濱河市榔昔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瘪菌,老刑警劉巖撒会,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異师妙,居然都是意外死亡诵肛,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門默穴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來怔檩,“玉大人,你說我怎么就攤上這事蓄诽≈橄矗” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵若专,是天一觀的道長许蓖。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么膊爪? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任自阱,我火速辦了婚禮,結(jié)果婚禮上米酬,老公的妹妹穿的比我還像新娘沛豌。我一直安慰自己,他們只是感情好赃额,可當(dāng)我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布加派。 她就那樣靜靜地躺著,像睡著了一般跳芳。 火紅的嫁衣襯著肌膚如雪芍锦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天飞盆,我揣著相機與錄音娄琉,去河邊找鬼。 笑死吓歇,一個胖子當(dāng)著我的面吹牛孽水,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播城看,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼女气,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了测柠?” 一聲冷哼從身側(cè)響起主卫,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鹃愤,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體完域,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡软吐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吟税。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凹耙。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖肠仪,靈堂內(nèi)的尸體忽然破棺而出肖抱,到底是詐尸還是另有隱情,我是刑警寧澤异旧,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布意述,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏荤崇。R本人自食惡果不足惜拌屏,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望术荤。 院中可真熱鬧倚喂,春花似錦、人聲如沸瓣戚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽子库。三九已至舱权,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間刚照,已是汗流浹背刑巧。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留无畔,地道東北人啊楚。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像浑彰,于是被迫代替她去往敵國和親恭理。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 1.初識volatile 下面這段代碼郭变,演示了一個使用 volatile 以及沒使用 volatile這個關(guān)鍵字颜价,...
    Mrsunup閱讀 345評論 0 0
  • [TOC] 安全性問題概述 什么是安全性問題 多線程情況下的安全問題,是指數(shù)據(jù)的一致性問題诉濒,在多線程環(huán)境下周伦,多個線...
    0x70e8閱讀 324評論 0 0
  • 線程安全性 線程安全性定義: 當(dāng)多個線程訪問某個類時,不管運行時環(huán)境采用何種調(diào)度方式或者這些進程將如何交替執(zhí)行未荒,并...
    端碗吹水閱讀 98評論 0 0
  • 1 什么是可見性? 通過 volatile 修飾的變量被a線程修改b線程能立即讀取到修改后的值,不會出現(xiàn)'臟讀' ...
    降龍_伏虎閱讀 805評論 0 5
  • 可見性 一個線程對主內(nèi)存的修改专挪,可以及時的被其他線程觀察到; 導(dǎo)致共享變量在線程間不可見的原因 線程交叉執(zhí)行片排; 重...
    烏魯木齊001號程序員閱讀 406評論 0 1