高效并發(fā)(一)——java內(nèi)存模型與線程

一嫂丙、內(nèi)存模型

1. 主內(nèi)存與工作內(nèi)存

虛擬機(jī)運(yùn)行時(shí)內(nèi)存區(qū)域:


image.png

虛擬機(jī)內(nèi)存模型


image.png

前者內(nèi)存區(qū)域中的Java堆、棧牲芋、方法區(qū)等和后者講的主內(nèi)存惠险、工作內(nèi)存并不是同一個(gè)層次的內(nèi)存劃分苗傅,這兩者基本上沒(méi)有關(guān)系,如果兩者一定要勉強(qiáng)對(duì)應(yīng)起來(lái)班巩,那從變量渣慕、主內(nèi)存、工作內(nèi)存的定義來(lái)看抱慌,主內(nèi)存主要對(duì)應(yīng)java堆中的對(duì)象實(shí)例數(shù)據(jù)部分逊桦,而工作內(nèi)存則對(duì)應(yīng)虛擬機(jī)中的部分區(qū)域。從更低層次上說(shuō)遥缕,主內(nèi)存就直接對(duì)應(yīng)于物理硬件的內(nèi)存卫袒,而為了獲取更好的運(yùn)行速度,虛擬機(jī)可能會(huì)讓工作內(nèi)存優(yōu)先存儲(chǔ)到寄存器和高速緩存中单匣,因?yàn)槌绦蜻\(yùn)行時(shí)主要訪問(wèn)讀寫的是工作內(nèi)存夕凝。

2. 內(nèi)存間交互操作

8種基本操作:
  1. lock,鎖定户秤,作用于主內(nèi)存變量码秉,它把一個(gè)變量標(biāo)識(shí)為一條線程獨(dú)占的狀態(tài)。
  2. unlock鸡号,解鎖转砖,解鎖后的變量才能被其他線程鎖定。
  3. read,讀取府蔗,作用于主內(nèi)存變量晋控,它把一個(gè)主內(nèi)存變量的值,讀取到工作內(nèi)存中姓赤。
  4. load赡译,載入,作用于工作內(nèi)存變量不铆,它把read讀取的值蝌焚,放到工作內(nèi)存的變量副本中。
  5. use誓斥,使用只洒,作用于工作內(nèi)存變量,它把工作內(nèi)存變量的值傳遞給執(zhí)行引擎劳坑,當(dāng)JVM遇到一個(gè)變量讀取指令就會(huì)執(zhí)行這個(gè)操作毕谴。
  6. assign,賦值泡垃,作用于工作內(nèi)存變量析珊,它把一個(gè)從執(zhí)行引擎接收到的值賦值給工作內(nèi)存變量。
  7. store蔑穴,存儲(chǔ),作用域工作內(nèi)存變量惧浴,它把工作內(nèi)存變量值傳送到主內(nèi)存中存和。
  8. write,寫入衷旅,作用于主內(nèi)存變量捐腿,它把store從工作內(nèi)存中得到的變量值寫入到主內(nèi)存變量中
8種操作的規(guī)則:

java內(nèi)存模型還規(guī)定了在執(zhí)行上述8種基本操作時(shí)必須滿足如下規(guī)則:

  1. 不允許read和load、store和write操作之一單獨(dú)出現(xiàn)柿顶,即不允許加載或同步工作到一半茄袖。
  2. 不允許一個(gè)線程丟棄它最近的assign操作,即變量在工作內(nèi)存中改變了之后嘁锯,必須吧改變化同步回主內(nèi)存宪祥。
  3. 不允許一個(gè)線程無(wú)原因地(無(wú)assign操作)把數(shù)據(jù)從工作內(nèi)存同步到主內(nèi)存中。
  4. 一個(gè)新的變量只能在主內(nèi)存中誕生家乘。
  5. 一個(gè)變量在同一時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作蝗羊,但lock操作可以被同一條線程重復(fù)執(zhí)行多次,仁锯,多次lock之后必須要執(zhí)行相同次數(shù)的unlock操作耀找,變量才會(huì)解鎖。
  6. 如果對(duì)一個(gè)對(duì)象進(jìn)行l(wèi)ock操作业崖,那會(huì)清空工作內(nèi)存變量中的值野芒,在執(zhí)行引擎使用這個(gè)變量前蓄愁,需要重新執(zhí)行l(wèi)oad或assign操作初始化變量的值。
  7. 如果一個(gè)變量事先沒(méi)有被lock狞悲,就不允許對(duì)它進(jìn)行unlock操作撮抓,也不允許去unlock一個(gè)被其他線程鎖住的變量。
  8. 對(duì)一個(gè)變量執(zhí)行unlock操作之前效诅,必須將此變量同步回主內(nèi)存中(執(zhí)行store胀滚、write)。
      有如上8種內(nèi)存訪問(wèn)操作以及規(guī)則限定,再加上對(duì)volatile的一些特殊規(guī)定,就已經(jīng)完全確定了java程序中哪些內(nèi)存訪問(wèn)操作是在并發(fā)下安全的松邪。

3. 對(duì)于volatile型變量的特殊規(guī)則

  • volatile有兩個(gè)特性:1纺弊、對(duì)所有線程可見(jiàn);2脆栋、防止指令重排;我們接下來(lái)說(shuō)明一下這兩個(gè)特性。
      可見(jiàn)性施掏,是指當(dāng)一條線程修改了某個(gè)volatile變量的值,新值對(duì)于其它線程來(lái)說(shuō)是可以立即知道的茅糜。而普通變量無(wú)法做到這點(diǎn)七芭。但這里有個(gè)誤區(qū),由于volatile對(duì)所有線程立即可見(jiàn)蔑赘,對(duì)volatile的寫操作會(huì)立即反應(yīng)到其它線程狸驳,因此基于volatile的變量的運(yùn)算在并發(fā)下是安全的。這是錯(cuò)誤的缩赛,原因是volatile所謂的其它線程立即知道耙箍,是其它線程在使用的時(shí)候會(huì)讀讀內(nèi)存然后load到自己工作內(nèi)存,如果這時(shí)候其它線程進(jìn)行了修改酥馍,本線程的volatile變量狀態(tài)會(huì)被置為無(wú)效辩昆,會(huì)重新讀取,但如果本線程的變量已經(jīng)被讀入執(zhí)行棧幀旨袒,那么是不會(huì)重新讀取的汁针;那么兩個(gè)線程都把本地工作內(nèi)存內(nèi)容寫入主存的時(shí)候就會(huì)發(fā)生覆蓋問(wèn)題,導(dǎo)致并發(fā)錯(cuò)誤峦失。
      防止指令重排扇丛,重排序優(yōu)化是機(jī)器級(jí)的操作,也就是硬件級(jí)別的操作尉辑。重排序會(huì)打亂代碼順序執(zhí)行帆精,但會(huì)保證在執(zhí)行過(guò)程中所有依賴賦值結(jié)果的地方都能獲取到正確的結(jié)果,因此在一個(gè)線程的方法執(zhí)行過(guò)程中無(wú)法感知到重排的操作影響,這也是“線程內(nèi)表現(xiàn)為串行”的由來(lái)卓练。volatile的屏蔽重排序在jdk1.5后才被修復(fù)隘蝎。原理是volatile生成的匯編代碼多了一條帶lock前綴的空操作的命令,而根據(jù)IA32手冊(cè)規(guī)定襟企,這個(gè)lock前綴會(huì)使得本cpu的緩存寫入內(nèi)存嘱么,而寫入動(dòng)作也會(huì)引起別的cpu或者別的內(nèi)核無(wú)效化,這相當(dāng)于對(duì)cpu緩存中的變量做了一次store跟write的操作顽悼,所以通過(guò)這樣一個(gè)操作曼振,可以讓變量對(duì)其它c(diǎn)pu立即可見(jiàn)(因?yàn)闋顟B(tài)被置為無(wú)效,用的話必須重新讀取)蔚龙。
      另外冰评,java內(nèi)存模型對(duì)volatile變量有三條特殊規(guī)則:
      a、每次使用變量之前都必須先從主內(nèi)存刷新最新的值木羹,用于保證能看見(jiàn)其它線程對(duì)變量的修改甲雅;
      b、每次對(duì)變量修改后都必須立刻同步到主內(nèi)存中坑填,用于保證其它線程可以看到自己的修改抛人;
      c、兩個(gè)變量都是volatile的脐瑰,將數(shù)據(jù)同步到內(nèi)存的時(shí)候妖枚,先讀的先寫;

  • 關(guān)于volatile變量的可見(jiàn)性的誤解:“volatile變量對(duì)所有線程立即可見(jiàn)的苍在,對(duì)volatile變量所有的寫操作都能立刻反映到其他線程中盅惜,換句話說(shuō),volatile變量在各個(gè)線程中是一致的忌穿,所以基于volatile變量的運(yùn)算在并發(fā)下是安全的”。這句話的論據(jù)部分并沒(méi)有錯(cuò)结啼,但是其論據(jù)并不能得出“基于volatile變量的運(yùn)算在并發(fā)下是安全的”這個(gè)結(jié)論掠剑。
    volatile變量在各個(gè)線程中的工作內(nèi)存中不存在一致性問(wèn)題(在各個(gè)線程的工作內(nèi)存中volatile變量也可以存在不一致的情況,但由于每次使用之前都要先刷新郊愧,執(zhí)行引擎看不到不一致的情況朴译,因此可以認(rèn)為不存在不一致問(wèn)題),但是Java里面的運(yùn)算并非原子操作属铁,導(dǎo)致volatile變量的運(yùn)算在并發(fā)下一樣是不安全的眠寿。

public class VolatileTest {
    public static volatile int race = 0;
    private static final int THREADS_COUNT = 20;
    
    public static void increase(){
        race++;
    }
    
    public static void main(String[] args) {
        Thread[] threads = new Thread[THREADS_COUNT];
        for(int i = 0; i < THREADS_COUNT; i++){
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for(int i = 0; i < 10000; i++){
                        increase();
                    }
                }
            });
            threads[i].start();
        }
        
        //等待所有累加線程都結(jié)束
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        
        System.out.println(race);
        //如果代碼正確并發(fā),輸出結(jié)果為200000焦蘑。但是每次運(yùn)行都不會(huì)得到期望的結(jié)果盯拱。
    }
}

通過(guò)javap反編譯:

public class com.jvm.thread.VolatileTest {
  public static volatile int race;

  static {};
    Code:
       0: iconst_0
       1: putstatic     #13                 // Field race:I
       4: return

  public com.jvm.thread.VolatileTest();
    Code:
       0: aload_0
       1: invokespecial #18                 // Method java/lang/Object."<init>":
()V
       4: return

  public static void increase();
    Code:
       0: getstatic     #13                 // Field race:I
       3: iconst_1
       4: iadd
       5: putstatic     #13                 // Field race:I
       8: return

  public static void main(java.lang.String[]);
    Code:
       0: bipush        20
       2: anewarray     #25                 // class java/lang/Thread
       5: astore_1
       6: iconst_0
       7: istore_2
       8: goto          37
      11: aload_1
      12: iload_2
      13: new           #25                 // class java/lang/Thread
      16: dup
      17: new           #27                 // class com/jvm/thread/VolatileTest
$1
      20: dup
      21: invokespecial #29                 // Method com/jvm/thread/VolatileTes
t$1."<init>":()V
      24: invokespecial #30                 // Method java/lang/Thread."<init>":
(Ljava/lang/Runnable;)V
      27: aastore
      28: aload_1
      29: iload_2
      30: aaload
      31: invokevirtual #33                 // Method java/lang/Thread.start:()V

      34: iinc          2, 1
      37: iload_2
      38: bipush        20
      40: if_icmplt     11
      43: goto          49
      46: invokestatic  #36                 // Method java/lang/Thread.yield:()V

      49: invokestatic  #39                 // Method java/lang/Thread.activeCou
nt:()I
      52: iconst_1
      53: if_icmpgt     46
      56: getstatic     #43                 // Field java/lang/System.out:Ljava/
io/PrintStream;
      59: getstatic     #13                 // Field race:I
      62: invokevirtual #49                 // Method java/io/PrintStream.printl
n:(I)V
      65: return
}

問(wèn)題就出現(xiàn)在自增運(yùn)算“race++”之中,用javap反編譯這段代碼,發(fā)現(xiàn)只有一行代碼的increase()方法在Class文件中由4條字節(jié)碼指令構(gòu)成(return指令不是由race++產(chǎn)生的狡逢,這條指令可以不計(jì)算)宁舰,從字節(jié)碼層面容易分析并發(fā)失敗的原因了:當(dāng)getstatic指令把race的值取到操作棧頂時(shí),volatile關(guān)鍵字保證了race的值在此時(shí)是正確的奢浑,但是在執(zhí)行iconst_1, iadd這些指令時(shí)蛮艰,其他線程可能已經(jīng)把race的值加大了,而操作棧頂?shù)闹稻妥兂闪诉^(guò)期的數(shù)據(jù)雀彼,所以putstatic指令執(zhí)行后就可能把較小的race值同步回主內(nèi)存中壤蚜。

客觀地說(shuō),此時(shí)使用字節(jié)碼來(lái)分析并發(fā)問(wèn)題徊哑,仍然是不嚴(yán)謹(jǐn)?shù)耐嗨ⅲ驗(yàn)榧词咕幾g出來(lái)只有一條指令,也并不意味執(zhí)行這條指令就是一個(gè)原子操作实柠。一條字節(jié)碼指令在解釋執(zhí)行時(shí)水泉,解釋器將要運(yùn)行許多行代碼才能實(shí)現(xiàn)它的語(yǔ)義,如果是編譯執(zhí)行窒盐,一條字節(jié)碼指令也可能轉(zhuǎn)化成若干條本地機(jī)器碼指令草则,此處使用 -XX:+PrintAssembly 參數(shù)輸出反編譯來(lái)分析會(huì)更加嚴(yán)謹(jǐn)一些。
由于volatile變量只能保證可見(jiàn)性蟹漓,在不符合以下兩條規(guī)則的運(yùn)算場(chǎng)景中炕横,我們?nèi)匀灰ㄟ^(guò)加鎖(使用synchronized或java.util.concurrent中的原子類)來(lái)保證原子性:

1)運(yùn)算結(jié)果并不依賴變量的當(dāng)前值,或者能夠確保只有單一的線程修改變量的值葡粒。

2)變量不需要與其他的狀態(tài)變量共同參與不變約束份殿。

4. 對(duì)于long和double型變量的特殊規(guī)則

對(duì)于64位的數(shù)據(jù)類型long跟double,java內(nèi)存模型定義了一條相對(duì)寬泛的規(guī)定:允許虛擬機(jī)將沒(méi)有被volatile修飾的64位數(shù)據(jù)操作分為兩次32位的操作來(lái)進(jìn)行嗽交。也就是允許虛擬機(jī)不保證64位數(shù)據(jù)load卿嘲、store、read跟write這4個(gè)操作的原子性夫壁,這就是long跟double的非原子性協(xié)定拾枣。如果真的這樣,當(dāng)多個(gè)線程共享一個(gè)并未聲明為volatile的long或者double類型的變量盒让,并同時(shí)對(duì)他們進(jìn)行讀取修改梅肤,那么某些線程可能會(huì)讀到一些既非初始值也不是其他線程修改值的代表了“半個(gè)變量”的數(shù)據(jù)。
  不過(guò)這種讀到“半個(gè)變量”的情況非常罕見(jiàn)邑茄,因?yàn)閖ava內(nèi)存模型雖然允許實(shí)現(xiàn)為非原子的但“強(qiáng)烈建議”將其實(shí)現(xiàn)為原子操作姨蝴,實(shí)際開(kāi)發(fā)中,所有商用虛擬機(jī)都將其實(shí)現(xiàn)為原子操作肺缕,因此左医,這點(diǎn)我們并不需要擔(dān)心授帕。

5. 原子性、可見(jiàn)性和有序性

原子性(Atomicity):由Java內(nèi)存模型來(lái)直接保證的原子性變量操作包括read炒辉、load豪墅、assign、use黔寇、store和write偶器,我們大致可以認(rèn)為基本數(shù)據(jù)類型的訪問(wèn)具備原子性(long和double例外)。

如果應(yīng)用場(chǎng)景需要一個(gè)更大范圍的原子性保證缝裤,Java內(nèi)存模型還提供了lock和unlock操作來(lái)滿足需求屏轰,盡管虛擬機(jī)未把lock和unlock操作直接開(kāi)放給用戶,但是卻提供了更高層次的字節(jié)碼指令monitorenter和monitorexit來(lái)隱式地使用這兩個(gè)操作憋飞,這兩個(gè)字節(jié)碼指令反應(yīng)到Java代碼中就是同步塊——synchronized關(guān)鍵字霎苗,因此在synchronized塊之間的操作也具備原子性。

可見(jiàn)性(Visibility):指當(dāng)一個(gè)線程修改了共享變量的值榛做,其他線程能夠立即得知這個(gè)修改唁盏。

除了volatile,Java還有兩個(gè)關(guān)鍵字能實(shí)現(xiàn)可見(jiàn)性检眯,synchronized和final厘擂。同步塊的可見(jiàn)性是由“對(duì)一個(gè)變量執(zhí)行unlock操作之前,必須把此變量同步回主內(nèi)存中(執(zhí)行store和write操作)”這條規(guī)則獲得的锰瘸,而final關(guān)鍵字的可見(jiàn)性是指:被final修飾的字段在構(gòu)造器中一旦被初始化完成刽严,并且構(gòu)造器沒(méi)有把“this”的引用傳遞出去(this引用逃逸是一件很危險(xiǎn)的事情,其他線程有可能通過(guò)這個(gè)引用訪問(wèn)到“初始化了一半”的對(duì)象)避凝,那么其他線程中就能看見(jiàn)final字段的值舞萄。

//變量i與j都具備可見(jiàn)性,它們無(wú)須同步就能被其他線程正確訪問(wèn)
    public static final int i;
    public final int j;
    
    static{
        i = 0;
        //do something
    }
    
    {
        //也可以選擇在構(gòu)造函數(shù)中初始化
        j = 0;
        //do something
    }

有序性(Ordering):Java程序中天然的有序性可以總結(jié)為一句話:如果在本線程內(nèi)觀察管削,所有的操作都是有序的倒脓;如果在一個(gè)線程中觀察另外一個(gè)線程,所有的操作都是無(wú)序的含思。前半句是指“線程內(nèi)表現(xiàn)為串行的語(yǔ)義”(Within-Thread As-if-Serial Semantics)把还,后半句是指“指令重排序”現(xiàn)象和“工作內(nèi)存與主內(nèi)存同步延遲”現(xiàn)象。

Java語(yǔ)言提供了volatile和synchronized兩個(gè)關(guān)鍵字來(lái)保證線程之間操作的有序性茸俭,volatile關(guān)鍵字本身就包含了禁止指令重排序的語(yǔ)義,而synchronized則是由“一個(gè)變量在同一時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作”這條規(guī)則獲得的安皱,這個(gè)規(guī)則決定了持有同一個(gè)鎖的兩個(gè)同步塊只能串行地進(jìn)入调鬓。

6. 先行發(fā)生原則

先行發(fā)生是Java內(nèi)存模型中定義的兩項(xiàng)操作之間的偏序關(guān)系,如果操作A先行發(fā)生于操作B酌伊,其實(shí)就是說(shuō)在發(fā)生操作B之前腾窝,操作A產(chǎn)生的影響能被操作B觀察到缀踪,“影響”包括修改了內(nèi)存中共享變量的值/發(fā)送了消息/調(diào)用了方法等。

    i = 1虹脯;//在線程A中執(zhí)行
    j = i;//在線程B中執(zhí)行
    i = 2;//在線程C中執(zhí)行
    //A先于B驴娃,但是C與B沒(méi)有先行關(guān)系,B存在讀取過(guò)期數(shù)據(jù)風(fēng)險(xiǎn)循集,不具備多線程安全性

下面是Java內(nèi)存模型下一些“天然的”先行發(fā)生關(guān)系唇敞,無(wú)須任何同步器協(xié)助就已經(jīng)存在,可直接在編碼中使用咒彤。如果兩個(gè)操作之間的關(guān)系不在此列疆柔,并且無(wú)法從下列規(guī)則推倒出來(lái),它們就沒(méi)有順序性的保障镶柱,虛擬機(jī)可以對(duì)它們進(jìn)行隨意地重排序旷档。
1)程序次序規(guī)則(Program Order Rule):在一個(gè)線程內(nèi),按照程序代碼順序歇拆,書(shū)寫在前面的操作先行發(fā)生于書(shū)寫在后面的操作鞋屈。準(zhǔn)確地來(lái)說(shuō)應(yīng)該是控制流順序而不是程序代碼順序,因?yàn)橐紤]分支/循環(huán)結(jié)構(gòu)故觅。

2)管程鎖定規(guī)則(Monitor Lock Rule):一個(gè)unlock操作先行發(fā)生于后面對(duì)同一鎖的lock操作厂庇。這里必須強(qiáng)調(diào)的是同一鎖,而“后面”是指時(shí)間上的先后順序逻卖。

3)volatile變量規(guī)則(Volatile Variable Rule):對(duì)一個(gè)volatile變量的寫操作先行發(fā)生于后面對(duì)這個(gè)變量的讀操作宋列,這里的“后面”是指時(shí)間上的先后順序。

4)線程啟動(dòng)規(guī)則(Thread Start Rule):Thread對(duì)象的start()方法先行發(fā)生于此線程的每一個(gè)動(dòng)作评也。

5)線程終止規(guī)則(Thread Termination Rule):線程中的所有操作都先行發(fā)生于對(duì)此線程的終止檢測(cè)炼杖,我們可以通過(guò)Thread.join()方法結(jié)束/Thread.isAlive()的返回值等手段檢測(cè)到線程已經(jīng)終止執(zhí)行。

6)線程中斷規(guī)則(Thread Interruption Rule):對(duì)線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生盗迟,可以通過(guò)Thread.interrupted()方法檢測(cè)到是否有中斷發(fā)生坤邪。

7)對(duì)象終結(jié)規(guī)則(Finalizer Rule):一個(gè)對(duì)象的初始化完成(構(gòu)造函數(shù)執(zhí)行結(jié)束)先行發(fā)生于它的finalize()方法的開(kāi)始。

8)傳遞性(Transitivity):如果操作A先行發(fā)生于操作B罚缕,操作B先行發(fā)生于操作C艇纺,那么操作A先行發(fā)生于操作C。

時(shí)間上的先后順序與先行發(fā)生原則之間基本沒(méi)有太大的關(guān)系邮弹,所以我們衡量并發(fā)安全問(wèn)題時(shí)不要受時(shí)間順序的干擾黔衡,一切必須以先行發(fā)生原則為準(zhǔn)。

二腌乡、java線程

1. 線程的實(shí)現(xiàn)

并發(fā)不一定依賴多線程盟劫,但是Java里面談?wù)摬l(fā),大多數(shù)與線程脫不開(kāi)關(guān)系与纽。

主流操作系統(tǒng)都提供了線程實(shí)現(xiàn)侣签,Java語(yǔ)言則提供了在不同硬件和操作系統(tǒng)平臺(tái)對(duì)線程的同一處理塘装,每個(gè)java.lang.Thread類的實(shí)例就代表了一個(gè)線程。Thread類與大部分Java API有著顯著的差別影所,它的所有關(guān)鍵方法都被聲明為Native蹦肴。在Java API中一個(gè)Native方法可能就意味著這個(gè)方法沒(méi)有使用或無(wú)法使用平臺(tái)無(wú)關(guān)的手段實(shí)現(xiàn)。正因?yàn)檫@個(gè)原因猴娩,我們這里的“線程的實(shí)現(xiàn)”而不是“Java線程的實(shí)現(xiàn)”阴幌。
實(shí)現(xiàn)線程主要三種方式:

  1. 使用內(nèi)核線程實(shí)現(xiàn)

內(nèi)核線程(Kernel Thread, KLT)就是直接由操作系統(tǒng)內(nèi)核(Kernel胀溺,下稱內(nèi)核)支持的線程裂七,這種線程由內(nèi)核來(lái)完成線程切換,內(nèi)核通過(guò)操縱調(diào)度器(Scheduler)對(duì)線程進(jìn)行調(diào)度仓坞,并負(fù)責(zé)將線程的任務(wù)映射到各個(gè)處理器上背零。每個(gè)內(nèi)核線程都可以看作是內(nèi)核的一個(gè)分身,這樣操作系統(tǒng)就有能力同時(shí)處理多件事情无埃,支持多線程的內(nèi)核就叫多線程內(nèi)核(Multi-Thread Kernel)徙瓶。

程序一般不會(huì)直接去使用內(nèi)核線程,而是去使用內(nèi)核線程的一種高級(jí)接口——輕量級(jí)進(jìn)程(Light Weight Process嫉称, LWP)侦镇,輕量級(jí)進(jìn)程就是我們通常意義上所講的線程,由于每個(gè)輕量級(jí)進(jìn)程都由一個(gè)內(nèi)核線程支持织阅,因此只有先支持內(nèi)核線程壳繁,才能有輕量級(jí)進(jìn)程。這種輕量級(jí)進(jìn)程與內(nèi)核線程之間1:1的關(guān)系稱為一對(duì)一的線程模型荔棉。

輕量級(jí)進(jìn)程的局限性:由于是基于內(nèi)核線程實(shí)現(xiàn)的闹炉,所以各種進(jìn)程操作,如創(chuàng)建/析構(gòu)及同步润樱,都需要進(jìn)行系統(tǒng)調(diào)用渣触。而系統(tǒng)調(diào)用的代價(jià)相對(duì)較高,需要在用戶態(tài)(User Mode)和內(nèi)核態(tài)(Kernel Mode)中來(lái)回切換壹若;每個(gè)輕量級(jí)進(jìn)程都需要有一個(gè)內(nèi)核線程的支持嗅钻,因此輕量級(jí)進(jìn)程需要消耗一定的內(nèi)核資源(如內(nèi)核線程的棧空間)店展,因此一個(gè)系統(tǒng)支持輕量級(jí)進(jìn)程是有限的养篓。


image.png
  1. 使用用戶線程實(shí)現(xiàn)

狹義上的用戶線程指的是完全建立在用戶空間的線程庫(kù)上,系統(tǒng)內(nèi)核不能感知到線程存在的實(shí)現(xiàn)赂蕴。用戶線程的建立/同步/銷毀和調(diào)度完全在用戶態(tài)完成觉至,不需要內(nèi)核的幫助。如果程序?qū)崿F(xiàn)得當(dāng)睡腿,這種線程不需要切換到內(nèi)核態(tài)语御,因此操作快速且低消耗,也可以支持規(guī)模更大的線程數(shù)量席怪,部分高性能數(shù)據(jù)庫(kù)中的多線程就是由用戶線程實(shí)現(xiàn)的应闯。這種進(jìn)程與用戶線程之間1:N的關(guān)系稱為一對(duì)多的線程模型。

  1. 使用用戶線程加輕量級(jí)進(jìn)程混合實(shí)現(xiàn)

既存在用戶線程挂捻,也存在輕量級(jí)進(jìn)程碉纺。

2. java線程的調(diào)度

線程調(diào)度是指系統(tǒng)為線程分配處理器使用權(quán)的過(guò)程。主要調(diào)度方式兩種:

使用協(xié)同調(diào)度的多線程系統(tǒng)刻撒,線程執(zhí)行時(shí)間由線程本身控制骨田,線程把自己的工作執(zhí)行完后,要主動(dòng)通知系統(tǒng)切換到另外一個(gè)線程上去声怔。優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單态贤。缺點(diǎn):執(zhí)行時(shí)間不可控制。

使用搶占調(diào)用的多線程系統(tǒng)醋火,每個(gè)線程由系統(tǒng)分配執(zhí)行時(shí)間悠汽,線程的切換不由線程本身決定。Java使用的就是這種線程調(diào)度方式芥驳。

Java提供10個(gè)級(jí)別的線程優(yōu)先級(jí)設(shè)置柿冲,不過(guò),線程優(yōu)先級(jí)不靠譜兆旬,因?yàn)镴ava線程是被映射到系統(tǒng)的原生線程上實(shí)現(xiàn)的假抄,所以線程調(diào)度最終還是由操作系統(tǒng)決定。

3. 狀態(tài)轉(zhuǎn)換

java語(yǔ)言定義了5種進(jìn)程狀態(tài)丽猬,在任意一個(gè)時(shí)間點(diǎn)宿饱,一個(gè)線程只能有且只有其中一種狀態(tài):

新建(New):創(chuàng)建尚未啟動(dòng)的線程處于這種狀態(tài)。

運(yùn)行(Runable):包括操作系統(tǒng)線程狀態(tài)中的Running和Ready宝鼓,處于此狀態(tài)的線程可能正在運(yùn)行刑棵,也可能等待著CPU為它分配執(zhí)行時(shí)間。

無(wú)限期等待(Waiting):處于這種狀態(tài)的線程不會(huì)被分配CPU執(zhí)行時(shí)間愚铡,它們要等待其他線程顯示地喚醒蛉签。以下方法會(huì)讓線程陷入無(wú)限期的等待狀態(tài):

沒(méi)有設(shè)置Timeout參數(shù)的Object.wait()方法。

沒(méi)有設(shè)置Timeout參數(shù)的Thread.join()方法沥寥。

LockSupport.park()方法碍舍。

限期等待(Timed Waiting):處于這種狀態(tài)的線程也不會(huì)被分配CPU執(zhí)行時(shí)間,不過(guò)無(wú)須等待被其他線程顯示地喚醒邑雅,在一定時(shí)間后由系統(tǒng)自動(dòng)喚醒片橡。以下方法會(huì)讓線程陷入限期的等待狀態(tài):

Thread.sleep()方法。

設(shè)置了Timeout參數(shù)的Object.wait()方法淮野。

設(shè)置了Timeout參數(shù)的Thread.join()方法捧书。

LockSupport.parkNanos()方法吹泡。

LockSupport.parkUntil()方法。

阻塞(Blocked):線程被阻塞了经瓷,“阻塞狀態(tài)”與“等待狀態(tài)”的區(qū)別是:“阻塞狀態(tài)”在等待獲取一個(gè)排它鎖爆哑,這個(gè)事件將在另外一個(gè)線程放棄這個(gè)鎖的時(shí)候發(fā)生;“等待狀態(tài)”則是在等待一段時(shí)間舆吮,或者喚醒動(dòng)作的發(fā)生揭朝。在程序進(jìn)入等待進(jìn)入同步塊區(qū)域的時(shí)候,線程將進(jìn)入這種狀態(tài)色冀。

結(jié)束(Terminated):已終止線程的線程狀態(tài)潭袱,線程已經(jīng)結(jié)束執(zhí)行。

image.png

參考:
https://www.cnblogs.com/java1024/p/8589537.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锋恬,一起剝皮案震驚了整個(gè)濱河市屯换,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌伶氢,老刑警劉巖趟径,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異癣防,居然都是意外死亡蜗巧,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門蕾盯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)幕屹,“玉大人,你說(shuō)我怎么就攤上這事级遭⊥希” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵挫鸽,是天一觀的道長(zhǎng)说敏。 經(jīng)常有香客問(wèn)我,道長(zhǎng)丢郊,這世上最難降的妖魔是什么盔沫? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮枫匾,結(jié)果婚禮上架诞,老公的妹妹穿的比我還像新娘。我一直安慰自己干茉,他們只是感情好谴忧,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般沾谓。 火紅的嫁衣襯著肌膚如雪委造。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,475評(píng)論 1 312
  • 那天均驶,我揣著相機(jī)與錄音争涌,去河邊找鬼。 笑死辣恋,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的模软。 我是一名探鬼主播伟骨,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼燃异!你這毒婦竟也來(lái)了携狭?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤回俐,失蹤者是張志新(化名)和其女友劉穎逛腿,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體仅颇,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡单默,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了忘瓦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搁廓。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖耕皮,靈堂內(nèi)的尸體忽然破棺而出境蜕,到底是詐尸還是另有隱情,我是刑警寧澤凌停,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布粱年,位于F島的核電站,受9級(jí)特大地震影響罚拟,放射性物質(zhì)發(fā)生泄漏台诗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一舟舒、第九天 我趴在偏房一處隱蔽的房頂上張望拉庶。 院中可真熱鬧,春花似錦秃励、人聲如沸氏仗。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)皆尔。三九已至呐舔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間慷蠕,已是汗流浹背珊拼。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留流炕,地道東北人澎现。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像每辟,于是被迫代替她去往敵國(guó)和親剑辫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

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