【多線程與并發(fā)】Java并發(fā)理論基礎(chǔ)

多線程與并發(fā)問題

為什么出現(xiàn)多線程

了解計(jì)算機(jī)的都知道,CPU痢士、內(nèi)存彪薛、I/O設(shè)備的速度是有很大差異的,為了合理利用CPU的高性能怠蹂,平衡三者的速度差異善延,計(jì)算機(jī)體系結(jié)構(gòu)、操作系統(tǒng)城侧、編譯程序都做出的貢獻(xiàn)易遣,主要體現(xiàn)為:

  • CPU:增加了緩存,以均衡與內(nèi)存的速度差異嫌佑;(導(dǎo)致可見性問題)
  • 操作系統(tǒng):增加了進(jìn)程豆茫、線程,以分時(shí)復(fù)用CPU屋摇,進(jìn)而均衡CPU與I/O設(shè)備的速度差異揩魂;(導(dǎo)致原子性問題)
  • 編譯程序:優(yōu)化指令執(zhí)行次序,使得緩存能夠得到跟家合理的利用炮温。(導(dǎo)致有序性問題)

多線程不安全示例

如果多線程對(duì)同一個(gè)共享數(shù)據(jù)進(jìn)行訪問而不采取同步操作火脉,那么操作的結(jié)果可能不一致。

來看看示例代碼:兩個(gè)線程柒啤,共同讀寫一個(gè)全局變量count,每個(gè)線程執(zhí)行10000次count++,count的最終結(jié)果會(huì)是20000嗎?

public class ThreadUnsafeExample implements Runnable{
    private static ThreadUnsafeExample unsafeExample = new ThreadUnsafeExample();
    private static int count;

    public static void main(String[] args) {
        Thread thread1 = new Thread(unsafeExample);
        Thread thread2 = new Thread(unsafeExample);
        thread1.start();
        thread2.start();
        try {
            // 等待兩個(gè)線程都運(yùn)行結(jié)束后忘分,再打印結(jié)果
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //期待結(jié)果是20000,但是結(jié)果會(huì)小于這個(gè)值
        System.out.println(count);
    }
    @Override
    public void run() {
        for (int k = 0; k < 10000; k++) {
            count++;
        }
    }
}

多次運(yùn)行結(jié)果: count最終值會(huì)小于等于20000

并發(fā)問題根源 — 并發(fā)三要素

并發(fā)三要素:

  • 可見性
  • 原子性
  • 有序性

上述代碼輸出為什么不是20000白修?并發(fā)出現(xiàn)問題的根源是什么妒峦?

可見性:CPU緩存引起

可見性:當(dāng)一個(gè)線程對(duì)共享變量修改,另一個(gè)線程能夠立即看到兵睛。

舉例說明:

int a = 0;
// 線程 A 執(zhí)行
a++; 
// 線程 B 執(zhí)行
System.out.print("a=" + a);

即使是在執(zhí)行完A線程里的a++后再執(zhí)行線程 B肯骇,線程 B 的輸入結(jié)果也會(huì)有 2 個(gè)種情況,一個(gè)是 0 和1祖很。

因?yàn)?a++ 在線程 A(CPU1)中做完了運(yùn)算笛丙,并沒有立刻更新到主內(nèi)存當(dāng)中,而線程B(CPU2)就去主內(nèi)存當(dāng)中讀取并打印假颇,此時(shí)打印的就是 0胚鸯。

原子性:分時(shí)復(fù)用引起

原子性:一個(gè)不可再被分割的顆粒。原子性指的是一個(gè)或多個(gè)操作要么全部執(zhí)行成功要么全部執(zhí)行失敗笨鸡。

舉例說明:

int a = 1;
//線程A執(zhí)行
i++;
//線程B執(zhí)行
i++;

i++是非原子操作姜钳,需要三條CPU指令:

  • i從內(nèi)存讀取到CPU寄存器坦冠;
  • 在CPU寄存器中執(zhí)行i + 1操作;
  • 將結(jié)果i寫入內(nèi)存

由于CPU分時(shí)復(fù)用(切換線程)的存在哥桥,線程A執(zhí)行了第一條指令后辙浑,就切換到線程B執(zhí)行,若線程B執(zhí)行了三條指令后拟糕,在切換回線程A執(zhí)行線程A的后續(xù)兩條指令判呕,將造成最后寫到內(nèi)存中的i值是2而不是3。

有序性:重排序引起

有序性:程序執(zhí)行的順序按照代碼的先后順序執(zhí)行送滞。

舉例說明:

//線程A
int a = -1;
boolean flag = false;
a = 1; //語句1
flag = true;// 語句2

//線程B
int b = 0;
if(flag){
  b = a;
}

處理器為了擁有更好的運(yùn)算效率侠草,會(huì)自動(dòng)優(yōu)化、重排序指令犁嗅,但會(huì)確保直接結(jié)果不變边涕。

上面代碼中,語句1 與語句2并沒有數(shù)據(jù)的依賴愧哟,如果運(yùn)行在單線程奥吩,把兩句代碼調(diào)換也不會(huì)出現(xiàn)問題哼蛆。

但若運(yùn)行在多線程中蕊梧,當(dāng)處理器把語句1和語句2進(jìn)行重排序,那么可能會(huì)導(dǎo)致結(jié)果不一致腮介。若線程A先執(zhí)行了語句2肥矢,此時(shí),語句1并未執(zhí)行叠洗,那么線程B中b的值會(huì)和預(yù)期結(jié)果有差異甘改。

在執(zhí)行程序時(shí)為了提高性能,編譯器和處理器常常會(huì)對(duì)指令做重排序灭抑。重排序分三種類型:

  • 編譯器優(yōu)化的重排序:編譯器在不改變單線程程序語義的前提下十艾,可以重新安排語句的執(zhí)行順序。
  • 指令級(jí)并行的重排序:現(xiàn)在處理器采用了指令級(jí)并行技術(shù)(Instruction-Level Parallelism腾节,ILP)來將多條指令重疊執(zhí)行忘嫉。如果不存在數(shù)據(jù)依賴性,處理器可以改變語句對(duì)應(yīng)機(jī)器指令的執(zhí)行順序案腺。
  • 內(nèi)存系統(tǒng)的重排序:由于處理器使用緩存和讀/寫緩沖區(qū)庆冕,這使得加載和存儲(chǔ)操作看上去可能是在亂序執(zhí)行。

從java源代碼到最終實(shí)際執(zhí)行的指令序列劈榨,會(huì)分別經(jīng)歷下面三種重排序:


java-jmm-3.png

上述的 1 屬于編譯器重排序访递,2 和 3 屬于處理器重排序。這些重排序都可能會(huì)導(dǎo)致多線程程序出現(xiàn)內(nèi)存可見性問題同辣。

  • 對(duì)于編譯器重排序:JMM的編譯器重排序規(guī)則會(huì)禁止特定類型的編譯器重排序(不是所有的編譯器重排序都要禁止)拷姿。
  • 對(duì)于處理器重排序:JMM的處理器重排序規(guī)則會(huì)要求java編譯器在生成指令序列時(shí)惭载,插入特定類型的內(nèi)存屏障(memory barriers,Intel稱之為memory fence)指令跌前,通過內(nèi)存屏障指令來禁止特定類型的處理器重排序(不是所有的處理器重排序都要禁止)棕兼。

內(nèi)存屏障(Memory Barrier,或有時(shí)叫做內(nèi)存柵欄抵乓,Memory Fence)是一種 CPU 指令伴挚,用來禁止處理器指令發(fā)生重排序(像屏障一樣),從而保障指令執(zhí)行的有序性灾炭。另外茎芋,為了達(dá)到屏障的效果,它也會(huì)使處理器寫入蜈出、讀取值之前田弥,將主內(nèi)存的值寫入高速緩存,清空無效隊(duì)列铡原,從而保障變量的可見性偷厦。

Java如何解決并發(fā)問題 — Java內(nèi)存模型(JMM)

JMM是一種虛擬機(jī)規(guī)范,用于屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問差異燕刻,已實(shí)現(xiàn)讓Java程序再各種平臺(tái)下都能達(dá)到一致的效果只泼。關(guān)于JMM后續(xù)會(huì)詳細(xì)解說,本篇文章我們先從下面兩個(gè)維度來理解JMM:

  • 第一個(gè)理解維度:核心知識(shí)點(diǎn)
  • 第二個(gè)理解維度:可見性卵洗,有序性请唱,原子性

第一個(gè)理解維度:核心知識(shí)點(diǎn)

JMM本質(zhì)上可以理解為:Java內(nèi)存模型規(guī)范了JVM如何提供按需禁用緩存編譯優(yōu)化的方法。

這些方法具體包括:

  • volatile过蹂、synchronized和final三個(gè)關(guān)鍵字
  • happens-before規(guī)則

關(guān)鍵字:volatile十绑、synchronized和final

關(guān)于這三個(gè)關(guān)鍵字后面會(huì)有文章進(jìn)行詳細(xì)解說,這里就先不展開酷勺。

happens-before規(guī)則

可以用 volatile 和 synchronized 來保證有序性本橙,除此之外,JVM 還規(guī)定了happens-before原則脆诉,happens-before 用于描述兩個(gè)操作的內(nèi)存可見性甚亭,通過保證可見性的機(jī)制可以讓應(yīng)用程序免于數(shù)據(jù)競(jìng)爭(zhēng)干擾。它的定義如下:

如果一個(gè)操作A happens-before 另一個(gè)操作B库说,那么操作A的執(zhí)行結(jié)果將對(duì)操作B可見狂鞋。

舉例說明:

private int value = 0;
//操作A
public void setValue(int value){
  value = 1;
}
//操作B
public int getValue(){
  return value;
}

假設(shè) setValue 就是操作 A,getValue 就是操作 B潜的。如果我們先后在兩個(gè)線程中調(diào)用 A 和 B骚揍,那最后在 B 操作中返回的 value 值是多少,有以下兩種情況:

  • 如果A happens-before B 不成立

    當(dāng)線程調(diào)用操作 B(getValue)時(shí),即使操作 A(setValue)已經(jīng)在其他線程中被調(diào)用過信不,并且 value 也被成功設(shè)置為 1嘲叔,但這個(gè)修改對(duì)于操作 B(getValue)仍然是不可見的。此時(shí)CPU緩存中的結(jié)果如果入內(nèi)存中則value值返回 1抽活,否則返回 0硫戈。

  • 如果A happens-before B 成立

    根據(jù) happens-before 的定義,先行發(fā)生動(dòng)作的結(jié)果下硕,對(duì)后續(xù)發(fā)生動(dòng)作是可見的丁逝。也就是說如果我們先在一個(gè)線程中調(diào)用了操作 A(setValue)方法,那么這個(gè)修改后的結(jié)果對(duì)后續(xù)的操作 B(getValue)始終可見梭姓。因此如果先調(diào)用 setValue 將 value 賦值為 1 后霜幼,后續(xù)在其他線程中調(diào)用 getValue 的值一定是 1。

那在 Java 中的兩個(gè)操作如何就算符合 happens-before 規(guī)則了呢誉尖? JMM 中定義了以下幾種情況是自動(dòng)符合 happens-before 規(guī)則的:

  1. 單一線程原則(Single Thread Rule)
    在一個(gè)線程內(nèi)罪既,在程序前面的操作先行發(fā)生于后面的操作。

在單線程內(nèi)部铡恕,如果一段代碼的字節(jié)碼順序也隱式符合 happens-before 原則琢感,那么邏輯順序靠前的字節(jié)碼執(zhí)行結(jié)果一定是對(duì)后續(xù)邏輯字節(jié)碼可見,只是后續(xù)邏輯中不一定用到而已探熔。比如以下代碼:

int a = 10;  // 1
b = b + 1;   // 2

當(dāng)代碼執(zhí)行到 2 處時(shí)驹针,a = 10 這個(gè)結(jié)果已經(jīng)是公之于眾的,至于用沒用到 a 這個(gè)結(jié)果則不一定祭刚。比如上面代碼就沒有用到 a = 10 的結(jié)果牌捷,說明 b 對(duì) a 的結(jié)果沒有依賴墙牌,這樣就有可能發(fā)生指令重排涡驮。

但如果b對(duì)a的結(jié)果沒有依賴,則不會(huì)發(fā)生指令重排優(yōu)化:

int a = 10;  // 1
b = b + a;   // 2
  1. 管程鎖定規(guī)則(Monitor Lock Rule)
    對(duì)一個(gè) volatile 變量的寫操作先行發(fā)生于后面對(duì)這個(gè)變量的讀操作喜滨。通俗的說就是如果A線程先寫了一個(gè)volatile變量捉捅,然后B線程去讀這個(gè)變量,那么這個(gè)寫操作一定是 happens-before 讀操作的虽风。

  2. volatile 變量規(guī)則(Volatile Variable Rule)
    對(duì)一個(gè) volatile 變量的寫操作先行發(fā)生于后面對(duì)這個(gè)變量的讀操作棒口。通俗的說就是如果A線程先寫了一個(gè)volatile變量,然后B線程去讀這個(gè)變量辜膝,那么這個(gè)寫操作一定是 happens-before 讀操作的无牵。

  3. 線程啟動(dòng)規(guī)則(Thread Start Rule)
    Thread 對(duì)象的 start() 方法先行發(fā)生于此線程的每一個(gè)動(dòng)作。假設(shè)線程 A 在執(zhí)行過程中厂抖,通過執(zhí)行 ThreadB.start() 來啟動(dòng)線程 B茎毁,那么線程A對(duì)共享變量的修改確保在線程B開始執(zhí)行后對(duì)線程B的可見。

  4. 線程終結(jié)規(guī)則(Thread Join Rule)
    Thread 對(duì)象的結(jié)束先行發(fā)生于 join() 方法返回。假定線程 A 在執(zhí)行的過程中七蜘,通過調(diào)用 ThreadB.join() 等待線程 B 終止谭溉,那么線程 B 在終止之前對(duì)共享變量的修改在線程 A 等待返回后可見。

  5. 線程中斷規(guī)則(Thread Interruption Rule)
    對(duì)線程 interrupt() 方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生橡卤,可以通過 interrupted() 方法檢測(cè)到是否有中斷發(fā)生扮念。
    這句話不是很好理解,舉例說明一下:在線程A中中斷線程B之前碧库,將共享變量x的值修改為100柜与,則當(dāng)線程B檢測(cè)到中斷事件時(shí),訪問到的x變量的值為100嵌灰。

//在線程A中將x變量的值初始化為0
    private int x = 0;

    public void execute(){
        //在線程A中初始化線程B
        Thread threadB = new Thread(()->{
            //線程B檢測(cè)自己是否被中斷
            if (Thread.currentThread().isInterrupted()){
                //如果線程B被中斷旅挤,則此時(shí)X的值為100
                System.out.println(x);
            }
        });
        //在線程A中啟動(dòng)線程B
        threadB.start();
        //在線程A中將共享變量X的值修改為100
        x = 100;
        //在線程A中中斷線程B
        threadB.interrupt();
    }
  1. 對(duì)象終結(jié)規(guī)則(Finalizer Rule)
    一個(gè)對(duì)象的初始化完成(構(gòu)造函數(shù)執(zhí)行結(jié)束)先行發(fā)生于它的 finalize() 方法的開始。

舉例:

public class TestThread {

   public TestThread(){
       System.out.println("構(gòu)造方法");
   }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("對(duì)象銷毀");
    }

    public static void main(String[] args){
        new TestThread();
        System.gc();
    }
}

運(yùn)行結(jié)果:

構(gòu)造方法
對(duì)象銷毀
  1. 傳遞性
    **如果操作 A happens-before 操作 B伞鲫,而操作 B happens-before 操作 C粘茄,則操作 A 一定 happens-before 操作 C。 **

第二個(gè)理解維度:可見性秕脓,有序性柒瓣,原子性

  • 可見性

在Java中,普通的共享變變量是不能保證可見性吠架,因?yàn)槠胀ü蚕碜兞勘恍薷闹筌狡叮裁磿r(shí)候被寫入主存是不確定的,當(dāng)其他線程去讀取時(shí)傍药,此時(shí)內(nèi)存中可能還是原來的舊值磺平,因此無法保證可見性。

  1. Java提供了volatile關(guān)鍵字來保證可見性拐辽。(當(dāng)一個(gè)共享變量被volatile修飾時(shí)拣挪,它會(huì)保證修改的值會(huì)立即被更新到主存,當(dāng)有其他線程需要讀取時(shí)俱诸,它會(huì)去內(nèi)存中讀取新值菠劝。)
  2. 通過synchronized和Lock也能夠保證可見性。(synchronized和Lock能保證同一時(shí)刻只有一個(gè)線程獲取鎖然后執(zhí)行同步代碼睁搭,并且在釋放鎖之前會(huì)將對(duì)變量的修改刷新到主存當(dāng)中赶诊。因此可以保證可見性。)
  • 有序性
  1. 可以通過volatile關(guān)鍵字來保證一定的“有序性”(happens-before原則中的volatile 變量規(guī)則)园骆。
  2. 還可以通過synchronized和Lock來保證有序性舔痪。(synchronized和Lock保證每個(gè)時(shí)刻是有一個(gè)線程執(zhí)行同步代碼,相當(dāng)于是讓線程順序執(zhí)行同步代碼锌唾,自然就保證了有序性锄码。)
  3. JMM是通過Happens-Before 規(guī)則來保證有序性的。
  • 原子性

在Java中,對(duì)基本數(shù)據(jù)類型的變量的讀取和賦值操作是原子性操作巍耗,即這些操作是不可被中斷的秋麸,要么執(zhí)行,要么不執(zhí)行炬太。先來看下列操作哪些是原子性的:

x = 10; //語句1:直接將數(shù)值10賦值給x灸蟆,也就是說線程執(zhí)行這個(gè)語句的會(huì)直接將數(shù)值10寫入到工作內(nèi)存中
y = x; //語句2:包含2個(gè)操作,操作1先去讀取x的值亲族,操作2再將x的值寫入工作內(nèi)存炒考,故不是原子性操作
x++; //語句3:包含3個(gè)操作,操作1讀取x的值霎迫,操作2進(jìn)行加1操作斋枢,操作3寫入新的值
x = x + 1; //語句4:同語句3

以上只有語句1的操作具備原子性。

也就是說知给,只有簡(jiǎn)單的讀取瓤帚、賦值(而且必須是將數(shù)字賦值給某個(gè)變量,變量之間的相互賦值不是原子操作)才是原子操作涩赢。

  1. 從上面可以看出戈次,Java內(nèi)存模型只保證了基本讀取和賦值是原子性操作;
  2. 若要實(shí)現(xiàn)更大范圍操作的原子性筒扒,可以通過ynchronized和Lock來實(shí)現(xiàn)怯邪。(由于synchronized和Lock能夠保證任一時(shí)刻只有一個(gè)線程執(zhí)行該代碼塊,那么自然可以保證原子性花墩。)

線程安全的實(shí)現(xiàn)方法

1. 同步互斥

synchronized 和 ReentrantLock悬秉。

后續(xù)文章會(huì)詳細(xì)分析。

2. 非阻塞同步

阻塞同步(同步互斥)與非阻塞同步

互斥同步主要的問題就是線程阻塞和喚醒鎖帶來的性能問題冰蘑,因此這種同步也稱為阻塞同步和泌。

互斥同步屬于一種悲觀的并發(fā)策略,總是認(rèn)為只要不去做正確的同步措施懂缕,那就肯定會(huì)出現(xiàn)問題允跑。無論共享數(shù)據(jù)是否真的會(huì)出現(xiàn)競(jìng)爭(zhēng)王凑,它都要進(jìn)行加鎖(這里討論的是概念模型搪柑,實(shí)際上虛擬機(jī)會(huì)優(yōu)化掉很大一部分不必要的加鎖)、用戶態(tài)核心態(tài)轉(zhuǎn)換索烹、維護(hù)鎖計(jì)數(shù)器和檢查是否有被阻塞的線程需要喚醒等操作工碾。

隨著硬件指令集的發(fā)展,我們可以使用基于沖突檢測(cè)的樂觀并發(fā)策略: 先進(jìn)行操作百姓,如果沒有其它線程爭(zhēng)用共享數(shù)據(jù)渊额,那操作就成功了,否則采取補(bǔ)償措施(不斷地重試,直到成功為止)旬迹。這種樂觀的并發(fā)策略的許多實(shí)現(xiàn)都不需要將線程阻塞火惊,因此這種同步操作稱為非阻塞同步。

CAS

樂觀鎖需要操作和沖突檢測(cè)這兩個(gè)步驟具備原子性奔垦,這里就不能再使用互斥同步來保證了屹耐,只能靠硬件來完成。

CAS 全稱是 Compare And Swap椿猎,譯為比較和替換惶岭,是一種通過硬件實(shí)現(xiàn)并發(fā)安全的常用技術(shù),底層通過利用 CPU 的 CAS 指令對(duì)緩存加鎖或總線加鎖的方式來實(shí)現(xiàn)多處理器之間的原子操作犯眠。它的實(shí)現(xiàn)過程主要有 3 個(gè)操作數(shù):內(nèi)存值 V按灶,舊的預(yù)期值 E,要修改的新值 U筐咧,當(dāng)且僅當(dāng)預(yù)期值 E和內(nèi)存值 V 相同時(shí)鸯旁,才將內(nèi)存值 V 修改為 U,否則什么都不做量蕊。

AtomicInteger

J.U.C 包里面的整數(shù)原子類 AtomicInteger羡亩,其中的 compareAndSet() 和 getAndIncrement() 等方法都使用了 Unsafe 類的 CAS 操作。

以下代碼使用了 AtomicInteger 執(zhí)行了自增的操作危融。

private AtomicInteger cnt = new AtomicInteger();

public void add() {
    cnt.incrementAndGet();
}

以下代碼是 incrementAndGet() 的源碼畏铆,它調(diào)用了 unsafe 的 getAndAddInt() 。

public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

以下代碼是 getAndAddInt() 源碼吉殃,var1 指示對(duì)象內(nèi)存地址辞居,var2 指示該字段相對(duì)對(duì)象內(nèi)存地址的偏移,var4 指示操作需要加的數(shù)值蛋勺,這里為 1瓦灶。通過 getIntVolatile(var1, var2) 得到舊的預(yù)期值,通過調(diào)用 compareAndSwapInt() 來進(jìn)行 CAS 比較抱完,如果該字段內(nèi)存地址中的值等于 var5贼陶,那么就更新內(nèi)存地址為 var1+var2 的變量為 var5+var4。

可以看到 getAndAddInt() 在一個(gè)循環(huán)中進(jìn)行巧娱,發(fā)生沖突的做法是不斷的進(jìn)行重試碉怔。

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

ABA問題

如果一個(gè)變量初次讀取的時(shí)候是 A 值,它的值被改成了 B禁添,后來又被改回為 A撮胧,那 CAS 操作就會(huì)誤認(rèn)為它從來沒有被改變過。

J.U.C 包提供了一個(gè)帶有標(biāo)記的原子引用類 AtomicStampedReference 來解決這個(gè)問題老翘,它可以通過控制變量值的版本來保證 CAS 的正確性芹啥。大部分情況下 ABA 問題不會(huì)影響程序并發(fā)的正確性锻离,如果需要解決 ABA 問題,改用傳統(tǒng)的互斥同步可能會(huì)比原子類更高效墓怀。

3. 無同步方案

要保證線程安全汽纠,并不是一定就要進(jìn)行同步。如果一個(gè)方法本來就不涉及共享數(shù)據(jù)傀履,那它自然就無須任何同步措施去保證正確性疏虫。

棧封閉

多個(gè)線程訪問同一個(gè)方法的局部變量時(shí),不會(huì)出現(xiàn)線程安全問題啤呼,因?yàn)榫植孔兞看鎯?chǔ)在虛擬機(jī)棧中卧秘,屬于線程私有的。

線程本地存儲(chǔ)(ThreadLocal)

如果一段代碼中所需要的數(shù)據(jù)必須與其他代碼共享官扣,那就看看這些共享數(shù)據(jù)的代碼是否能保證在同一個(gè)線程中執(zhí)行翅敌。如果能保證,我們就可以把共享數(shù)據(jù)的可見范圍限制在同一個(gè)線程之內(nèi)惕蹄,這樣蚯涮,無須同步也能保證線程之間不出現(xiàn)數(shù)據(jù)爭(zhēng)用的問題。

可以使用ThreadLocal 類來實(shí)現(xiàn)線程本地存儲(chǔ)功能卖陵。

后續(xù)會(huì)出ThreadLocal的詳細(xì)分析遭顶。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末删顶,一起剝皮案震驚了整個(gè)濱河市伊约,隨后出現(xiàn)的幾起案子祠饺,更是在濱河造成了極大的恐慌汹碱,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件朴恳,死亡現(xiàn)場(chǎng)離奇詭異窜骄,居然都是意外死亡蛾魄,警方通過查閱死者的電腦和手機(jī)餐曹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門逛拱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人台猴,你說我怎么就攤上這事朽合。” “怎么了饱狂?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵曹步,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我嗡官,道長(zhǎng)箭窜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任衍腥,我火速辦了婚禮磺樱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘婆咸。我一直安慰自己竹捉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布尚骄。 她就那樣靜靜地躺著块差,像睡著了一般。 火紅的嫁衣襯著肌膚如雪倔丈。 梳的紋絲不亂的頭發(fā)上憨闰,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音需五,去河邊找鬼鹉动。 笑死,一個(gè)胖子當(dāng)著我的面吹牛宏邮,可吹牛的內(nèi)容都是我干的泽示。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼蜜氨,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼械筛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起飒炎,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤埋哟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后郎汪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體定欧,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年怒竿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了砍鸠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡耕驰,死狀恐怖爷辱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情朦肘,我是刑警寧澤饭弓,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站媒抠,受9級(jí)特大地震影響弟断,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜趴生,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一阀趴、第九天 我趴在偏房一處隱蔽的房頂上張望昏翰。 院中可真熱鬧,春花似錦刘急、人聲如沸棚菊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽统求。三九已至,卻和暖如春据块,著一層夾襖步出監(jiān)牢的瞬間码邻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工另假, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留像屋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓浪谴,卻偏偏與公主長(zhǎng)得像开睡,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子苟耻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

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