【原創(chuàng)】Java并發(fā)編程系列2:線程概念與基礎(chǔ)操作

【原創(chuàng)】Java并發(fā)編程系列2:線程概念與基礎(chǔ)操作

偉大的理想只有經(jīng)過忘我的斗爭和犧牲才能勝利實現(xiàn)。

本篇為【Dali王的技術(shù)博客】Java并發(fā)編程系列第二篇薪寓,講講有關(guān)線程的那些事兒硼端。主要內(nèi)容是如下這些:

  • 線程概念
  • 線程基礎(chǔ)操作

線程概念

進(jìn)程代表了運行中的程序屋匕,一個運行的Java程序就是一個進(jìn)程旗笔。在Java中倒脓,當(dāng)我們啟動main函數(shù)時就啟動了一個JVM的進(jìn)程,而main函數(shù)所在的線程就是這個進(jìn)程中的一個線程里伯,稱為主線程城瞎。
進(jìn)程和線程的關(guān)系如下圖所示:


file

由上圖可以看出來,一個進(jìn)程中有多個線程疾瓮,多個線程共享進(jìn)程的堆的方法區(qū)資源脖镀,但是每個線程有自己的程序計數(shù)器和棧區(qū)域。

線程基礎(chǔ)操作

線程創(chuàng)建與運行

Java中有三種線程創(chuàng)建方式狼电,分別為:繼承Thread類并重寫run方法蜒灰,實現(xiàn)Runnable接口的run方法,使用FutureTask方式肩碟。
先看繼承Thread方式的實現(xiàn)卷员,代碼示例如下:

public class ThreadDemo {
    public static class DemoThread extends Thread {
        @Override
        public void run() {
            System.out.println("this is a child thread.");
        }
    }
    public static void main(String[] args) {
        System.out.println("this is main thread.")
        DemoThread thread = new DemoThread();
        thread.start();
    }
}

上面代碼中DemoThread類繼承了Thread類,并重寫了run方法腾务。在main函數(shù)里創(chuàng)建了一個DemoThread的實例,然后調(diào)用其start方法啟動了線程削饵。

tips:調(diào)用start方法后線程并沒有馬上執(zhí)行岩瘦,而是處于就緒狀態(tài)未巫,也就是這個線程已經(jīng)獲取了除CPU資源外的其他資源,等待獲取CPU資源后才會真正處于運行狀態(tài)启昧。
使用繼承方式叙凡,好處在于通過this就可以獲取當(dāng)前線程,缺點在于Java不支持多繼承密末,如果繼承了Thread類握爷,那么就不能再繼承其他類。而且任務(wù)與代碼耦合嚴(yán)重严里,一個線程類只能執(zhí)行一個任務(wù)新啼,使用Runnable則沒有這個限制。
來看實現(xiàn)Runnable接口的run方法的方式刹碾,代碼示例如下:

public class RunnableDemo {
    public static class DemoRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("this is a child thread.");
        }
    }
    public static void main(String[] args) {
        System.out.println("this is main thread.");
        DemoRunnable runnable = new DemoRunnable();
        new Thread(runnable).start();
        new Thread(runnable).start();
    }
}

上面代碼兩個線程共用一個Runnable邏輯燥撞,如果需要,可以給RunnableTask添加參數(shù)進(jìn)行任務(wù)區(qū)分迷帜。在Java8中物舒,可以使用Lambda表達(dá)式對上述代碼進(jìn)行簡化:

 public static void main(String[] args) {
    System.out.println("this is main thread.");
    Thread t = new Thread(() -> System.out.println("this is child thread"));
    t.start();
}

上面兩種方式都有一個缺點,就是任務(wù)沒有返回值戏锹,下面看第三種冠胯,使用FutureTask的方式。代碼示例如下:

public class CallableDemo implements Callable<JsonObject> {
    @Override
    public JsonObject call() throws Exception {
        return new JsonObject();
    }
    public static void main(String[] args) {
        System.out.println("this is main thread.");
        FutureTask<JsonObject> futureTask = new FutureTask<>(new CallableDemo());   // 1. 可復(fù)用的FutureTask
        new Thread(futureTask).start();
        try {
            JsonObject result = futureTask.get();
            System.out.println(result.toString());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        // 2. 一次性的FutureTask
        FutureTask<JsonObject> innerFutureTask = new FutureTask<>(() -> {
            JsonObject jsonObject = new JsonObject();
            jsonObject.addProperty("name", "Dali");
            return jsonObject;
        });
        new Thread(innerFutureTask).start();

        try {
            JsonObject innerResult = innerFutureTask.get();
            System.out.println(innerResult.toString());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

如上代碼锦针,CallableDemo實現(xiàn)了Callable接口的call方法荠察,在main函數(shù)中使用CallableDemo的實例創(chuàng)建了一個FutureTask,然后使用創(chuàng)建的FutureTask對象作為任務(wù)創(chuàng)建了一個線程并啟動它伞插,最后通過FutureTask等待任務(wù)執(zhí)行完畢并返回結(jié)果割粮。
同樣的,上面的操作過程適合于需要復(fù)用的任務(wù)媚污,如果對于一次性的任務(wù)舀瓢,大可以通過Lambda來簡化代碼,如注釋2處耗美。

等待線程終止

在項目中經(jīng)常會遇到一個場景京髓,就是需要等待某幾件事情完成后才能繼續(xù)往下執(zhí)行。Thread類中有一個join方法就可以用來處理這種場景商架。直接上代碼示例:

    public static void main(String[] args) throws InterruptedException {
        System.out.println("main thread starts");
        Thread t1 = new Thread(() -> System.out.println("this is thread 1"));
        Thread t2 = new Thread(() -> System.out.println("this is thread 2"));
        t1.start();
        t2.start();
        System.out.println("main thread waits child threads to be over");
        t1.join();
        t2.join();
        System.out.println("child threads are over");
    }

上面代碼在主線程里啟動了兩個線程堰怨,然后分別調(diào)用了它們的join方法,主線程會在調(diào)用t1.join()后被阻塞蛇摸,等待其執(zhí)行完畢后返回备图;然后主線程調(diào)用t2.join()后再次被阻塞,等待t2執(zhí)行完畢后返回。上面代碼的執(zhí)行結(jié)果如下:

main thread starts
main thread waits child threads to be over
this is thread 1
this is thread 2
child threads are over

需要注意的是揽涮,線程1調(diào)用線程2的join方法后會被阻塞抠藕,當(dāng)其他線程調(diào)用了線程1的interrupt方法中斷了線程1時,線程1會拋出一個InterruptedException異常而返回蒋困。

讓線程睡眠

Thread類中有一個static的sleep方法盾似,當(dāng)一個執(zhí)行中的線程調(diào)用了Thread的sleep方法后,調(diào)用線程會暫時讓出指定時間的執(zhí)行權(quán)雪标,也就是在這期間不參與CPU的調(diào)度零院,但是該線程所擁有的監(jiān)視器資源,比如鎖還是不讓出的村刨。指定的睡眠時間到了后該函數(shù)會正常返回告抄,線程就處于就緒狀態(tài),然后等待CPU的調(diào)度執(zhí)行烹困。

tips:面試當(dāng)中wait和sleep經(jīng)常會被用來比較玄妈,需要多加體會二者的區(qū)別。
調(diào)用某個對象的wait()方法髓梅,相當(dāng)于讓當(dāng)前線程交出此對象的monitor拟蜻,然后進(jìn)入等待狀態(tài),等待后續(xù)再次獲得此對象的鎖枯饿;notify()方法能夠喚醒一個正在等待該對象的monitor的線程酝锅,當(dāng)有多個線程都在等待該對象的monitor的話,則只能喚醒其中一個線程奢方,具體喚醒哪個線程則不得而知搔扁。
調(diào)用某個對象的wait()方法和notify()方法,當(dāng)前線程必須擁有這個對象的monitor蟋字,因此調(diào)用wait()方法和notify()方法必須在同步塊或者同步方法中進(jìn)行(synchronized塊或者synchronized方法)稿蹲。

看一個線程睡眠的代碼示例:

private static final Lock lock = new ReentrantLock();
public static void main(String[] args) {
    Thread t1 = new Thread(() -> {
       // 獲取獨占鎖
       lock.lock();
       System.out.println("thread1 get to sleep");
        try {
            Thread.sleep(1000);
            System.out.println("thread1 is awake");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    });
    Thread t2 = new Thread(() -> {
        // 獲取獨占鎖
        lock.lock();
        System.out.println("thread2 get to sleep");
        try {
            Thread.sleep(1000);
            System.out.println("thread2 is awake");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    });

    t1.start();
    t2.start();
}

上面的代碼創(chuàng)建了一個獨占鎖,然后創(chuàng)建了兩個線程鹊奖,每個線程在內(nèi)部先獲取鎖苛聘,然后睡眠,睡眠結(jié)束后會釋放鎖忠聚。執(zhí)行結(jié)果如下:

thread1 get to sleep
thread1 is awake
thread2 get to sleep
thread2 is awake

從執(zhí)行結(jié)果來看设哗,線程1先獲取鎖,然后睡眠两蟀,再被喚醒网梢,之后才輪到線程2獲取到鎖,也即在線程1sleep期間赂毯,線程1并沒有釋放鎖战虏。
需要注意的是拣宰,如果子線程在睡眠期間,主線程中斷了它活烙,子線程就會在調(diào)用sleep方法處拋出了InterruptedException異常徐裸。

線程讓出CPU

Thread類中有一個static的yield方法,當(dāng)一個線程調(diào)用yield方法時啸盏,實際就是暗示線程調(diào)度器當(dāng)前線程請求讓出自己的CPU使用,如果該線程還有沒用完的時間片也會放棄骑祟,這意味著線程調(diào)度器可以進(jìn)行下一輪的線程調(diào)度了回懦。
當(dāng)一個線程調(diào)用yield方法時,當(dāng)前線程會讓出CPU使用權(quán)次企,然后處于就緒狀態(tài)怯晕,線程調(diào)度器會從線程就緒隊列里面獲取一個線程優(yōu)先級最高的線程,當(dāng)然也有可能會調(diào)度到剛剛讓出CPU的那個線程來獲取CPU執(zhí)行權(quán)缸棵。
請看代碼示例:

public static void main(String[] args) {
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 10; i++) {
            if (i == 8) {
                System.out.println("current thread: " + Thread.currentThread() + " yield cpu");
            }
            Thread.yield(); // 2
        }
        System.out.println("current thread: " + Thread.currentThread() + " is over");
    });

    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 10; i++) {
            if (i == 8) {
                System.out.println("current thread: " + Thread.currentThread() + " yield cpu");
            }
            Thread.yield(); // 1
        }
        System.out.println("current thread: " + Thread.currentThread() + " is over");
    });
    t1.start();
    t2.start();
}

在如上的代碼中舟茶,兩個線程的功能一樣,運行多次堵第,同一線程的兩行輸出是順序的吧凉,但是整體順序是不確定的,取決于線程調(diào)度器的調(diào)度情況踏志。
當(dāng)把上面代碼中1和2處代碼注釋掉阀捅,會發(fā)現(xiàn)結(jié)果只有一個,如下:

current thread: Thread[Thread-1,5,main] yield cpu
current thread: Thread[Thread-0,5,main] yield cpu
current thread: Thread[Thread-1,5,main] is over
current thread: Thread[Thread-0,5,main] is over

從結(jié)果可知针余,Thread.yiled方法生效使得兩個線程分別在執(zhí)行過程中放棄CPU饲鄙,然后在調(diào)度另一個線程,這里的兩個線程有點互相謙讓的感覺圆雁,最終是由于只有兩個線程忍级,最終還是執(zhí)行完了兩個任務(wù)。

tips:sleep和yield的區(qū)別:
當(dāng)線程調(diào)用sleep方法時伪朽,調(diào)用線程會阻塞掛起指定的時間轴咱,在這期間線程調(diào)度器不會去調(diào)度該線程。而調(diào)用yield方法時驱负,線程只是讓出自己剩余的時間片嗦玖,并沒有被阻塞掛起,而是出于就緒狀態(tài)跃脊,線程調(diào)度器下一次調(diào)度時就可能調(diào)度到當(dāng)前線程執(zhí)行宇挫。

線程中斷

Java中的線程中斷是一種線程間的協(xié)作模式。每個線程對象里都有一個boolean類型的標(biāo)識(通過isInterrupted()方法返回)酪术,代表著是否有中斷請求(interrupt()方法)器瘪。例如翠储,當(dāng)線程t1想中斷線程t2,只需要在線程t1中將線程t2對象的中斷標(biāo)識置為true橡疼,然后線程2可以選擇在合適的時候處理該中斷請求援所,甚至可以不理會該請求,就像這個線程沒有被中斷一樣欣除。
在上面章節(jié)中也講到了線程中斷的一些內(nèi)容住拭,此處就不再用代碼來展開了。

Java并發(fā)編程大綱

繼續(xù)附上Java編程的系統(tǒng)學(xué)習(xí)大綱以供參考:

Java并發(fā)編程.png
file

【參考資料】

  1. 《Java并發(fā)編程之美》

本文由微型公眾號【Dali王的技術(shù)博客】原創(chuàng)历帚,掃碼關(guān)注獲取更多原創(chuàng)技術(shù)文章滔岳。
[圖片上傳失敗...(image-68f97d-1584803811516)]

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市挽牢,隨后出現(xiàn)的幾起案子谱煤,更是在濱河造成了極大的恐慌,老刑警劉巖禽拔,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刘离,死亡現(xiàn)場離奇詭異,居然都是意外死亡睹栖,警方通過查閱死者的電腦和手機硫惕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來磨淌,“玉大人疲憋,你說我怎么就攤上這事×褐唬” “怎么了缚柳?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長搪锣。 經(jīng)常有香客問我秋忙,道長,這世上最難降的妖魔是什么构舟? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任灰追,我火速辦了婚禮,結(jié)果婚禮上狗超,老公的妹妹穿的比我還像新娘弹澎。我一直安慰自己,他們只是感情好努咐,可當(dāng)我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布苦蒿。 她就那樣靜靜地躺著,像睡著了一般渗稍。 火紅的嫁衣襯著肌膚如雪佩迟。 梳的紋絲不亂的頭發(fā)上团滥,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機與錄音报强,去河邊找鬼灸姊。 笑死,一個胖子當(dāng)著我的面吹牛秉溉,可吹牛的內(nèi)容都是我干的力惯。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼召嘶,長吁一口氣:“原來是場噩夢啊……” “哼夯膀!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起苍蔬,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蝴蜓,沒想到半個月后碟绑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡茎匠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年格仲,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诵冒。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡凯肋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出汽馋,到底是詐尸還是另有隱情侮东,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布豹芯,位于F島的核電站悄雅,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏铁蹈。R本人自食惡果不足惜宽闲,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望握牧。 院中可真熱鬧容诬,春花似錦、人聲如沸沿腰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽矫俺。三九已至吱殉,卻和暖如春掸冤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背友雳。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工稿湿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人押赊。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓饺藤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親流礁。 傳聞我的和親對象是個殘疾皇子涕俗,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,465評論 2 348

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