java線程基礎

前言- CPU競爭策略

操作系統(tǒng)中畅卓,CPU競爭有很多種策略。Unix系統(tǒng)使用的是時間片算法,而Windows則屬于搶占式的颅停。

  1. 在時間片算法中惫搏,所有的進程排成一個隊列具温。操作系統(tǒng)按照他們的順序,給每個進程分配一段時間筐赔,即該進程允許運行的時間铣猩。如果在 時間片結束時進程還在運行,則CPU將被剝奪并分配給另一個進程茴丰。如果進程在時間片結束前阻塞或結束剂习,則CPU當即進行切換。調度程 序所要做的就是維護一張就緒進程列表较沪,當進程用完它的時間片后鳞绕,它被移到隊列的末尾。

  2. 搶占式操作系統(tǒng)尸曼,就是說如果一個進程得到了 CPU 時間们何,除非它自己放棄使用 CPU ,否則將完全霸占 CPU 控轿。因此可以看出冤竹,在搶 占式操作系統(tǒng)中,操作系統(tǒng)假設所有的進程都是“人品很好”的茬射,會主動退出 CPU 鹦蠕。

在搶占式操作系統(tǒng)中,假設有若干進程在抛,操作系統(tǒng)會根據(jù)他們的優(yōu)先級钟病、饑餓時間(已經(jīng)多長時間沒有使用過 CPU 了),給他們算出一 個總的優(yōu)先級來刚梭。操作系統(tǒng)就會把 CPU 交給總優(yōu)先級最高的這個進程肠阱。當進程執(zhí)行完畢或者自己主動掛起后,操作系統(tǒng)就會重新計算一 次所有進程的總優(yōu)先級朴读,然后再挑一個優(yōu)先級最高的把 CPU 控制權交給他屹徘。

1-線程的優(yōu)先級

在java線程中,可以在構造線程時通過setPriority()方法設定線程的優(yōu)先級衅金,優(yōu)先級為從1-10的整數(shù)(默認為5)噪伊,優(yōu)先級越高系統(tǒng)分配的時間就越多簿煌;這里有一個設置優(yōu)先級的一個常用經(jīng)驗知識:對于頻繁阻塞的線程(經(jīng)常休眠,IO操作等)需要設置較高的優(yōu)先級鉴吹,因為這些經(jīng)常阻塞的線程即使設置為較高的優(yōu)先級姨伟,但是在大部分時間里,處于阻塞狀態(tài)拙寡,會讓出CPU授滓;而對于偏重計算的(將會長時間獨占CPU)線程設置為較低的優(yōu)先級,防止其他線程的不會長時間得不到執(zhí)行肆糕。

Thread thread = new Thread(job);
thread.setPriority(10);
thread.start();

注意:但是在很多系統(tǒng)下面對線程優(yōu)先級的設置可能無效(如類unix的分時系統(tǒng))

2-線程的狀態(tài)

給定一個時刻般堆,線程只能處于6種狀態(tài)其中的一種狀態(tài)

  1. NEW:初始狀態(tài),線程被構建诚啃,但是還沒有調用start()方法淮摔。
  2. RUNNABLE:運行狀態(tài),java線程將操作系統(tǒng)中的就緒狀態(tài)和運行兩種狀態(tài)籠統(tǒng)的稱作運行中始赎。
  3. BLOCKED:阻塞狀態(tài)和橙,特指線程阻塞于鎖synchronized關鍵字修飾的方法或者方法塊),并將該線程加入同步隊列中造垛。
  4. WAITING:等待狀態(tài)魔招,表示線程進入等待狀態(tài),進入該狀態(tài)表示當前線程需要等待其他線程做出一些特定的動作(比如通知或者中斷)五辽,并將該線程加入等待隊列中办斑。需要注意的是調用LockSupport.park()方法和Thread.join()會使得線程進入這個狀態(tài),而不是阻塞狀態(tài)杆逗。
  5. TIME_WAITING:超時等待狀態(tài)乡翅,該狀態(tài)是WAITING狀態(tài)的超時版本,它可以在指定的時間自行返回罪郊。一般由帶有超時設置的方法調用引起蠕蚜。
  6. TERMINATED:終止狀態(tài),表示當前線程已經(jīng)執(zhí)行完畢悔橄。
線程狀態(tài)轉移圖,源自《java并發(fā)編程的藝術》

注意上圖 Object.join()有誤靶累,應改成Thread.join()

線程start()和run()方法的區(qū)別
  • thread.start()方法

調用此方法將會由操作系統(tǒng)任務調度器在新創(chuàng)建的線程中執(zhí)行run()方法,可能不會立刻執(zhí)行橄维,由任務調度器調度尺铣,但是一定是在新創(chuàng)建的線程中執(zhí)行。重復調用start方法將拋出異常IllegalThreadStateException

  • thread.run()方法

Thread實現(xiàn)了Runnable接口争舞,默認實現(xiàn)是調用target的run方法,調用此方法并不會再新創(chuàng)建的線程去執(zhí)行run方法澈灼,只會在調用Thread.run()方法的線程本地執(zhí)行竞川,和調用一個普通對象的一個方法效果一樣店溢,可以被重復調用。

線程方法
  • Thread.sleep(long n)靜態(tài)方法
  1. 當n = 0 時委乌,thread 線程主動放棄自己CPU控制權床牧,進入就緒狀態(tài)。這種情況下只能調度優(yōu)先級相等或者更高的線程遭贸,低優(yōu)先級的線程很有能永遠得不到執(zhí)行戈咳,當沒有符合條件的線程時,當前會一直占用CPU壕吹,造成CPU滿載著蛙。
  1. 當n > 0 時,Thread線程將會被強制放棄CPU控制權耳贬,并睡眠n毫秒踏堡,進入阻塞狀態(tài)。這種情況下所有其他任意優(yōu)先級就緒的線程都有機會競爭CPU控制權咒劲。無論有沒有符合的線程顷蟆,都會放棄CPU控制權-,因此CPU占用率較低腐魂。
  2. 上述1帐偎、2是從線程調度的角度分析的,無論1蛔屹、2削樊,都不會釋放對象的鎖,也就是說如果有synchronized方法塊判导,其他線程仍然不能訪問共享數(shù)據(jù)嫉父,該方法拋出中斷異常。
  • thread.join()

使得調用thread.join()語句的線程等待thread線程的執(zhí)行完畢眼刃,才從這個語句返回绕辖,并繼續(xù)這個線程,該方法也需要捕獲中斷異常擂红。這個方法含有超時重載版本

  • Thread.yield()靜態(tài)方法

將thread線程放入就緒隊列中仪际,而不是同步隊列,由操作系統(tǒng)去調度昵骤。如果沒有找到其他就緒的線程树碱,則當前線程繼續(xù)運行,比thread.sleep(0)速度快变秦,只能讓相同優(yōu)先級的線程得以運行成榜。

重點分析join方法的實現(xiàn)


如何實現(xiàn)join方法的語義?

  1. ** 方法內(nèi)部調用Object.wait()方法進行等待蹦玫。**
  2. 當線程終止時赎婚,會調用線程自身的notifyAll()方法刘绣,通知所有等待在該線程對象監(jiān)視器上的線程。

屬于經(jīng)典的等待/通知模式

例子




解析:假設有兩個線程A挣输、B纬凤,在B中調用方法A.join,由于join是同步方法撩嚼,線程B排他獲取方法所屬的對象監(jiān)視器鎖停士,即線程對象A的監(jiān)視器鎖;線程B獲取線程A的對象監(jiān)視器鎖成功后完丽,在join方法內(nèi)部恋技,調用的是this.wait()方法,即在線程B在線程A對象上等待并釋放線程A上的對象監(jiān)視器鎖舰涌。

方法內(nèi)部有兩個循環(huán)判斷:

  1. join(0):Object.wait(0)猖任,在第一個while循環(huán)里始終對線程A是否終止進行判斷,如果還在運行瓷耙,則使線程B等待朱躺,直到被通知或者中斷,當被喚醒時還得去判斷線程A是否終止搁痛,如果終止則在獲取監(jiān)視器鎖后從join方法返回繼續(xù)代碼长搀,否則繼續(xù)等待。
  2. join(millis > 0) : Object.wait(millis)分析方法和上面基本一樣鸡典,只不過加了超時返回源请,即從wait方法返回時判斷是否超時,如果超時則在獲取對象鎖后跳出循環(huán)彻况,從join方法返回繼續(xù)執(zhí)行谁尸。
對象方法

object.wait(),object.notify()纽甘,object.notifyAll()

  1. 這3個方法在使用之前都要獲取object對象的鎖良蛮,即在synchronized(object){ object.wait();}
  2. 調用wait()方法后,線程狀態(tài)將由running變?yōu)閣aiting悍赢,并將當前線程放置到等待隊列中决瞳,并釋放object上的鎖。
  3. notify() 和notifyAll()方法調用后左权,等待線程依舊不會從wait方法返回皮胡,而是將等待隊列的一個或全部的線程移動到同步隊列中,被移動的線程狀態(tài)變?yōu)閎locked赏迟,然后通知線程從同步方法塊返回屡贺,并釋放object上鎖,只有下一次鎖的競爭中,等待線程成功獲取到object上的鎖烹笔,才從wait方法返回裳扯。

3-線程的創(chuàng)建

提供了三個方法來創(chuàng)建Thread

  • 繼承Thread類來創(chuàng)建線程類抛丽,重寫run()方法作為線程執(zhí)行體谤职。

缺點:
線程類繼承了Thread類,無法在繼承其他父類亿鲜。
因為每條線程都是一個Thread子類的實例允蜈,因此多個線程之間共享數(shù)據(jù)比較麻煩。

  • 用實現(xiàn)了Runnable接口的對象作為target來創(chuàng)建線程對象蒿柳。

推薦饶套,用來將沒有返回值和不會拋出異常的方法體run()傳遞給線程去執(zhí)行

  • 用實現(xiàn)了Callable接口的對象作為task提交給線程池ExecutorService 通過submit方法來提交執(zhí)行

推薦,用來將有返回值和會拋出異常的方法體run()傳遞給線程去執(zhí)行

4-線程中斷

中斷是一種線程之間通信機制垒探,但是中斷不像其名字一樣會讓線程中斷妓蛮,而是線程通過循環(huán)判斷查看中斷標志位,來及時的查看中斷狀態(tài)并采取下一步的操作圾叼。

  1. 其他線程通過該線程的interrupt()方法對其進行中斷操作蛤克。
  2. 線程通過調用自身的isInterrupted()來進行判斷是否被中斷,也可以調用靜態(tài)方法Thread.interrupted()將清除當前線程的中斷標志并返回之前線程的中斷狀態(tài)夷蚊;如果該線程已經(jīng)處于終結狀態(tài)构挤,無論是否中斷,則調用該對象的isInterrupted()都將返回false惕鼓。
  3. 拋出InterruptedException異常的方法筋现,比如Thread.sleep(),這些方法在拋出異常之前箱歧,Java虛擬機會先將該線程的中斷標志位清除矾飞,然后再拋出InterruptedException異常,這時在調用isInterrupted()方法進行判斷將返回false呀邢。

5-等待/通知的經(jīng)典線程間通信范式

  • 等待方遵循如下原則
  1. 獲取對象的鎖洒沦。
  1. 如果條件不滿足,那么調用對象的wait()方法驼鹅,被通知后還要檢查條件微谓。
  2. 條件滿足則執(zhí)行對應的邏輯 。
synchronized(object對象){
        while(條件不滿足){
              object.wait();
        }
        對應的處理邏輯;
}
  • 通知方遵循如下原則
  1. 獲取對象的鎖输钩。
  1. 改變條件
  2. 通知所有等待在對象上的線程
synchronized(object對象){
        改變條件
        object.notifyAll();
}
6-ThreadLocal

線程本地變量豺型,是一個以TreadLocal變量為鍵,任意對象為值的存儲結構买乃,將變量與線程綁定在一起姻氨,為每一個線程維護一個獨立的變量副本,ThreadLocal將變量的范圍限制在一個線程的上下文當中剪验,使得變量的作用域為線程級別肴焊。

  1. ThreadLocal僅僅是個變量訪問的入口;
  2. 每一個Thread對象都有一個ThreadLocalMap對象前联,這個ThreadLocalMap持有所有已經(jīng)初始化的ThreadLocal值對象的引用;
  3. 只有在線程中調用ThreadLocal的set(),或者get()方法時都會在當前線程中綁定這個變量,否則不會綁定娶眷。第一次get()方法調用將會進行初始化(如果set方法沒有調用過)似嗤,而且初始化每個線程值進行一次。
  4. 初始化方法

允許對默認初始化方法進行重寫

// 默認初始化方法
protected T initialValue(){
        return null;
}

ThreadLocal源碼分析

  1. set()
// ThreadLocal.java
public void set(T value) {
   //1.首先獲取當前線程對象
   Thread t = Thread.currentThread();
   //2.獲取該線程對象的ThreadLocalMap
       ThreadLocalMap map = getMap(t);
       //如果map不為空届宠,執(zhí)行set操作烁落,以當前threadLocal對象為key
       //實際存儲對象為value進行set操作
       if (map != null)
           map.set(this, value);
       //如果map為空,則為該線程創(chuàng)建ThreadLocalMap
       else
           createMap(t, value);
   }
ThreadLocalMap getMap(Thread t) {
   //線程對象持有ThreadLocalMap的引用
   return t.threadLocals;
}
// Thread.java
ThreadLocal.ThreadLocalMap threadLocals = null;
  1. get()
public T get() {
    //1.首先獲取當前線程
    Thread t = Thread.currentThread();
    //2.獲取線程的map對象
    ThreadLocalMap map = getMap(t);
    //3.如果map不為空豌注,以threadlocal實例為key獲取到對應Entry伤塌,然后從Entry中取出對象即可。
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    //如果map為空轧铁,也就是第一次沒有調用set直接get
    //(或者調用過set每聪,又調用了remove)時,為其設定初始值
    return setInitialValue();
 }
private T setInitialValue() {
    T value = initialValue();//獲取初始值
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

場景一:為每一個線程分配一個遞增無重復的ID

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadLocalDemo {
    public static void main(String []args){
        for(int i=0;i<5;i++){
            final Thread t = new Thread(){
                @Override
                public void run(){
                    System.out.println("當前線程:"+Thread.currentThread().getName()
                        +",已分配ID:"+ThreadId.get());
                }
            };
            t.start();
        }
    }
    static class ThreadId{
        //一個遞增的序列齿风,使用AtomicInger原子變量保證線程安全
        private static final AtomicInteger nextId = new AtomicInteger(0);
        //線程本地變量药薯,為每個線程關聯(lián)一個唯一的序號
        private static final ThreadLocal<Integer> threadId =
                new ThreadLocal<Integer>() {
                    @Override
                    protected Integer initialValue() {
                        //相當于nextId++,由于nextId++這種操作是個復合操作而非原子操作,
                        //會有線程安全問題(可能在初始化時就獲取到相同的ID聂宾,所以使用原子變量
                        return nextId.getAndIncrement();
                    }
                };

       //返回當前線程的唯一的序列果善,如果第一次get,會先調用initialValue系谐,后面看源碼就了解了
        public static int get() {
            return threadId.get();
        }
    }
}

說明:ThreadID是線程共享的巾陕,所以需要原子類來保證線程訪問的安全性,而ThreadID的成員變量threadId是線程封閉的纪他,只是線程本地變量初始化時需要訪問原子類(多個線程同時訪問引起 )

場景二:web開發(fā)中鄙煤,為每一個連接創(chuàng)建一個ThreadLocal保存session信息,如果web服務器使用線程池技術(比如Tomcat)進行線程復用茶袒,則每一次連接都要重新的set梯刚,以保證session為本次連接的信息。當session結束薪寓,調用remove方法亡资,將線程本地變量從線程的ThreadLocalMap中移除。

7-等待超時

主要學習的是剩余時間的計算

等待超時模式的經(jīng)典模式

// 同步方法
public synchronized Object get(long millss) throws InterruptedException {
    // 獲取將來時間點
    long future = System.currentTimeMillis)() + millis;
    // 初始化剩余時間為millis,從這可以看出超時等待時間并不是十分的嚴格
    long remaining = millis;

    // 超時等待判斷向叉,當返回值的結果不滿足并且剩余時間小于0時锥腻,從循環(huán)退出
    while((result == null) && remaining > 0){
        wait(remaining);
        remaining = future - System.currentTimeMillis();
    }
    return result;
}

參考鏈接
https://hacpai.com/article/1488015279637

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市母谎,隨后出現(xiàn)的幾起案子瘦黑,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幸斥,死亡現(xiàn)場離奇詭異匹摇,居然都是意外死亡,警方通過查閱死者的電腦和手機甲葬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門廊勃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人演顾,你說我怎么就攤上這事供搀。” “怎么了钠至?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長胎源。 經(jīng)常有香客問我棉钧,道長,這世上最難降的妖魔是什么涕蚤? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任宪卿,我火速辦了婚禮,結果婚禮上万栅,老公的妹妹穿的比我還像新娘佑钾。我一直安慰自己,他們只是感情好烦粒,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布休溶。 她就那樣靜靜地躺著,像睡著了一般扰她。 火紅的嫁衣襯著肌膚如雪兽掰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天徒役,我揣著相機與錄音孽尽,去河邊找鬼。 笑死忧勿,一個胖子當著我的面吹牛杉女,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鸳吸,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼熏挎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了层释?” 一聲冷哼從身側響起婆瓜,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后廉白,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體个初,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年猴蹂,在試婚紗的時候發(fā)現(xiàn)自己被綠了院溺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡磅轻,死狀恐怖珍逸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情聋溜,我是刑警寧澤谆膳,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站撮躁,受9級特大地震影響漱病,放射性物質發(fā)生泄漏。R本人自食惡果不足惜把曼,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一杨帽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嗤军,春花似錦注盈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至纠俭,卻和暖如春沿量,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背冤荆。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工朴则, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人钓简。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓乌妒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親外邓。 傳聞我的和親對象是個殘疾皇子撤蚊,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

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

  • 線程的狀態(tài) 新建狀態(tài):用new語句創(chuàng)建的線程對象處于新建狀態(tài),此時它和其它的java對象一樣损话,僅僅在堆中被分配了內(nèi)...
    稻田上的稻草人閱讀 559評論 0 1
  • java線程基礎 線程和進程進程 : 進程是系統(tǒng)進行資源分配和調度的一個獨立單位侦啸。進程由程序槽唾、數(shù)據(jù)和進程控制塊三部...
    你好667閱讀 204評論 0 0
  • 寫在前面的話: 這篇博客是我從這里“轉載”的,為什么轉載兩個字加“”呢光涂?因為這絕不是簡單的復制粘貼庞萍,我花了五六個小...
    SmartSean閱讀 4,711評論 12 45
  • 本文主要講了java中多線程的使用方法、線程同步忘闻、線程數(shù)據(jù)傳遞钝计、線程狀態(tài)及相應的一些線程函數(shù)用法、概述等齐佳。 首先講...
    李欣陽閱讀 2,442評論 1 15
  • Java多線程學習 [-] 一擴展javalangThread類 二實現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 2,952評論 1 18