【阿里面試系列】Java線程的應(yīng)用及挑戰(zhàn)

文章簡(jiǎn)介

上一篇文章【「阿里面試系列」搞懂并發(fā)編程叁扫,輕松應(yīng)對(duì)80%的面試場(chǎng)景】我們了解了進(jìn)程和線程的發(fā)展歷史地来、線程的生命周期乘寒、線程的優(yōu)勢(shì)和使用場(chǎng)景,這一篇万栅,我們從Java層面更進(jìn)一步了解線程的使用佑钾。關(guān)注我的技術(shù)公眾號(hào)【架構(gòu)師修煉寶典】一周出產(chǎn)1-2篇技術(shù)文章。Q群725219329分享并發(fā)編程烦粒,分布式休溶,微服務(wù)架構(gòu),性能優(yōu)化扰她,源碼兽掰,設(shè)計(jì)模式,高并發(fā)义黎,高可用禾进,Spring,Netty廉涕,tomcat泻云,JVM等技術(shù)視頻艇拍。

內(nèi)容導(dǎo)航

  1. 并發(fā)編程的挑戰(zhàn)
  2. 線程在Java中的使用

并發(fā)編程的挑戰(zhàn)

引入多線程的目的在第一篇提到過,就是為了充分利用CPU是的程序運(yùn)行得更快宠纯,當(dāng)然并不是說啟動(dòng)的線程越多越好卸夕。在實(shí)際使用多線程的時(shí)候,會(huì)面臨非常多的挑戰(zhàn)

線程安全問題

線程安全問題值的是當(dāng)多個(gè)線程訪問同一個(gè)對(duì)象時(shí)婆瓜,如果不考慮這些運(yùn)行時(shí)環(huán)境采用的調(diào)度方式或者這些線程將如何交替執(zhí)行快集,并且在代碼中不需要任何同步操作的情況下,這個(gè)類都能夠表現(xiàn)出正確的行為廉白,那么這個(gè)類就是線程安全的
比如下面的代碼是一個(gè)單例模式个初,在代碼的注釋出,如果多個(gè)線程并發(fā)訪問猴蹂,則會(huì)出現(xiàn)多個(gè)實(shí)例院溺。導(dǎo)致無法實(shí)現(xiàn)單例的效果

public class SingletonDemo {
   private static SingletonDemo singletonDemo=null;
   private SingletonDemo(){}
    public static SingletonDemo getInstance(){
        if(singletonDemo==null){/***線程安全問題***/
           singletonDemo=new SingletonDemo();
        }
        return singletonDemo;
    }
}

通常來說,我們把多線程編程中的線程安全問題歸類成如下三個(gè)磅轻,至于每一個(gè)問題的本質(zhì)珍逸,在后續(xù)的文章中我們會(huì)單獨(dú)講解

  1. 原子性
  2. 可見性
  3. 有序性

上下文切換問題

在單核心CPU架構(gòu)中,對(duì)于多線程的運(yùn)行是基于CPU時(shí)間片切換來實(shí)現(xiàn)的偽并行聋溜。由于時(shí)間片非常短導(dǎo)致用戶以為是多個(gè)線程并行執(zhí)行谆膳。而一次上下文切換,實(shí)際就是當(dāng)前線程執(zhí)行一個(gè)時(shí)間片之后切換到另外一個(gè)線程撮躁,并且保存當(dāng)前線程執(zhí)行的狀態(tài)這個(gè)過程漱病。上下文切換會(huì)影響到線程的執(zhí)行速度,對(duì)于系統(tǒng)來說意味著會(huì)消耗大量的CPU時(shí)間

減少上下文切換的方式

  1. 無鎖并發(fā)編程馒胆,在多線程競(jìng)爭(zhēng)鎖時(shí)缨称,會(huì)導(dǎo)致大量的上下文切換凝果。避免使用鎖去解決并發(fā)問題可以減少上下文切換
  2. CAS算法祝迂,CAS是一種樂觀鎖機(jī)制,不需要加鎖
  3. 使用與硬件資源匹配合適的線程數(shù)

死鎖

在解決線程安全問題的場(chǎng)景中器净,我們會(huì)比較多的考慮使用鎖型雳,因?yàn)樗褂帽容^簡(jiǎn)單。但是鎖的使用如果不恰當(dāng)山害,則會(huì)引發(fā)死鎖的可能性纠俭,一旦產(chǎn)生死鎖,就會(huì)造成比較嚴(yán)重的問題:產(chǎn)生死鎖的線程會(huì)一直占用鎖資源浪慌,導(dǎo)致其他嘗試獲取鎖的線程也發(fā)生死鎖冤荆,造成系統(tǒng)崩潰

以下是死鎖的簡(jiǎn)單案例

public class DeadLockDemo {
    //定義鎖對(duì)象
    private final Object lockA = new Object();
    private final Object lockB = new Object();
    private void deadLock(){
        new Thread(()->{
            synchronized (lockA){
                try {
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lockB){
                    System.out.println("Lock B");
                }
            }
        }).start();
        new Thread(()->{
            synchronized (lockB){
                synchronized (lockA){
                    System.out.println("Lock A");
                }
            }
        }).start();
    }
    public static void main(String[] args) {
        new DeadLockDemo().deadLock();
    }
}

通過jstack分析死鎖

1.首先通過jps獲取當(dāng)前運(yùn)行的進(jìn)程的pid

6628 Jps
17588 RemoteMavenServer
19220 Launcher
19004 DeadLockDemo

2.jstack打印堆棧信息,輸入 jstack19004, 會(huì)打印如下日志,可以很明顯看到死鎖的信息提示

Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x000000001d461e68 (object 0x000000076b310df8, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x000000001d463258 (object 0x000000076b310e08, a java.lang.Object),
  which is held by "Thread-1"

解決死鎖的手段
1.保證多個(gè)線程按照相同的順序獲取鎖
2.設(shè)置獲取鎖的超時(shí)時(shí)間权纤,超過設(shè)定時(shí)間以后自動(dòng)釋放
3.死鎖檢測(cè)

資源限制

資源限制主要指的是硬件資源和軟件資源钓简,在開發(fā)多線程應(yīng)用時(shí)乌妒,程序的執(zhí)行速度受限于這兩個(gè)資源。硬件的資源限制無非就是磁盤外邓、CPU撤蚊、內(nèi)存、網(wǎng)絡(luò)损话;軟件資源的限制有很多侦啸,比如數(shù)據(jù)庫連接數(shù)、計(jì)算機(jī)能夠支持的最大連接數(shù)等
資源限制導(dǎo)致的問題最直觀的體現(xiàn)就是前面說的上下文切換丧枪,也就是CPU資源和線程資源的嚴(yán)重不均衡導(dǎo)致頻繁上下文切換光涂,反而會(huì)造成程序的運(yùn)行速度下降

資源限制的主要解決方案,就是缺啥補(bǔ)啥拧烦。CPU不夠用顶捷,可以增加CPU核心數(shù);一臺(tái)機(jī)器的資源有限屎篱,則增加多臺(tái)機(jī)器來做集群服赎。

線程在Java中的使用

在Java中實(shí)現(xiàn)多線程的方式比較簡(jiǎn)單,因?yàn)镴ava中提供了非常方便的API來實(shí)現(xiàn)多線程交播。
1.繼承Thread類實(shí)現(xiàn)多線程
2.實(shí)現(xiàn)Runnable接口
3.實(shí)現(xiàn)Callable接口通過Future包裝器來創(chuàng)建Thread線程重虑,這種是帶返回值的線程
4.使用線程池ExecutorService

關(guān)注我的技術(shù)公眾號(hào)【架構(gòu)師修煉寶典】一周出產(chǎn)1-2篇技術(shù)文章。Q群725219329分享并發(fā)編程秦士,分布式缺厉,微服務(wù)架構(gòu),性能優(yōu)化隧土,源碼提针,設(shè)計(jì)模式,高并發(fā)曹傀,高可用辐脖,Spring,Netty皆愉,tomcat嗜价,JVM等技術(shù)視頻。

繼承Thread類

繼承Thread類幕庐,然后重寫run方法久锥,在run方法中編寫當(dāng)前線程需要執(zhí)行的邏輯。最后通過線程實(shí)例的start方法來啟動(dòng)一個(gè)線程

public class ThreadDemo extends Thread{
    @Override
    public void run() {
        //重寫run方法异剥,提供當(dāng)前線程執(zhí)行的邏輯
        System.out.println("Hello world");
    }
    public static void main(String[] args) {
        ThreadDemo threadDemo=new ThreadDemo();
        threadDemo.start();
    }
}

Thread類其實(shí)是實(shí)現(xiàn)了Runnable接口瑟由,因此Thread自己也是一個(gè)線程實(shí)例,但是我們不能直接用 newThread().start()去啟動(dòng)一個(gè)線程冤寿,原因很簡(jiǎn)單歹苦,Thread類中的run方法是沒有實(shí)際意義的绿鸣,只是一個(gè)調(diào)用通過構(gòu)造函數(shù)傳遞寄來的另一個(gè)Runnable實(shí)現(xiàn)類的run方法,這塊的具體演示會(huì)在Runnable接口的代碼中看到

public
class Thread implements Runnable {
    /* What will be run. */
    private Runnable target;
    ...
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    ...

實(shí)現(xiàn)Runnable接口

如果需要使用線程的類已經(jīng)繼承了其他的類暂氯,那么按照J(rèn)ava的單一繼承原則潮模,無法再繼承Thread類來實(shí)現(xiàn)線程,所以可以通過實(shí)現(xiàn)Runnable接口來實(shí)現(xiàn)多線程

public class RunnableDemo implements Runnable{
    @Override
    public void run() {
        //重寫run方法痴施,提供當(dāng)前線程執(zhí)行的邏輯
        System.out.println("Hello world");
    }
    public static void main(String[] args) {
        RunnableDemo runnableDemo=new RunnableDemo();
        new Thread(runnableDemo).start();
    }
}

上面的代碼中擎厢,實(shí)現(xiàn)了Runnable接口,重寫了run方法辣吃;接著為了能夠啟動(dòng)RunnableDemo這個(gè)線程动遭,必須要實(shí)例化一個(gè)Thread類,通過構(gòu)造方法傳遞一個(gè)Runnable接口實(shí)現(xiàn)類去啟動(dòng)神得,Thread的run方法就會(huì)調(diào)用target.run來運(yùn)行當(dāng)前線程,代碼在上面.

實(shí)現(xiàn)Callable接口

在有些多線程使用的場(chǎng)景中厘惦,我們有時(shí)候需要獲取異步線程執(zhí)行完畢以后的反饋結(jié)果,也許是主線程需要拿到子線程的執(zhí)行結(jié)果來處理其他業(yè)務(wù)邏輯哩簿,也許是需要知道線程執(zhí)行的狀態(tài)宵蕉。那么Callable接口可以很好的實(shí)現(xiàn)這個(gè)功能

public class CallableDemo implements Callable<String>{
    @Override
    public String call() throws Exception {
        return "hello world";
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<String> callable=new CallableDemo();
        FutureTask<String> task=new FutureTask<>(callable);
        new Thread(task).start();
        System.out.println(task.get());//獲取線程的返回值
    }
}

在上面代碼案例中的最后一行 task.get()就是獲取線程的返回值,這個(gè)過程是阻塞的节榜,當(dāng)子線程還沒有執(zhí)行完的時(shí)候羡玛,主線程會(huì)一直阻塞直到結(jié)果返回

使用線程池

為了減少頻繁創(chuàng)建線程和銷毀線程帶來的性能開銷,在實(shí)際使用的時(shí)候我們會(huì)采用線程池來創(chuàng)建線程宗苍,在這里我不打算展開多線程的好處和原理稼稿,我會(huì)在后續(xù)的文章中單獨(dú)說明。

public class ExecutorServiceDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //創(chuàng)建一個(gè)固定線程數(shù)的線程池
        ExecutorService pool = Executors.newFixedThreadPool(1);
        Future future=pool.submit(new CallableDemo()); 
        System.out.println(future.get());
    }
}

pool.submit有幾個(gè)重載方法讳窟,可以傳遞帶返回值的線程實(shí)例让歼,也可以傳遞不帶返回值的線程實(shí)例,源代碼如下

/*01*/Future<?> submit(Runnable task);
/*02*/<T> Future<T> submit(Runnable task, T result);
/*03*/<T> Future<T> submit(Callable<T> task);

關(guān)注我的技術(shù)公眾號(hào)【架構(gòu)師修煉寶典】一周出產(chǎn)1-2篇技術(shù)文章丽啡。Q群725219329分享并發(fā)編程谋右,分布式,微服務(wù)架構(gòu)碌上,性能優(yōu)化倚评,源碼,設(shè)計(jì)模式馏予,高并發(fā),高可用盔性,Spring霞丧,Netty,tomcat冕香,JVM等技術(shù)視頻蛹尝。

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末后豫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子突那,更是在濱河造成了極大的恐慌挫酿,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,686評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件愕难,死亡現(xiàn)場(chǎng)離奇詭異早龟,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)猫缭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,668評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門葱弟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人猜丹,你說我怎么就攤上這事芝加。” “怎么了射窒?”我有些...
    開封第一講書人閱讀 158,160評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵藏杖,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我脉顿,道長(zhǎng)制市,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,736評(píng)論 1 284
  • 正文 為了忘掉前任弊予,我火速辦了婚禮祥楣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘汉柒。我一直安慰自己误褪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,847評(píng)論 6 386
  • 文/花漫 我一把揭開白布碾褂。 她就那樣靜靜地躺著兽间,像睡著了一般。 火紅的嫁衣襯著肌膚如雪正塌。 梳的紋絲不亂的頭發(fā)上嘀略,一...
    開封第一講書人閱讀 50,043評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音乓诽,去河邊找鬼帜羊。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鸠天,可吹牛的內(nèi)容都是我干的讼育。 我是一名探鬼主播,決...
    沈念sama閱讀 39,129評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼奶段!你這毒婦竟也來了饥瓷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,872評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤痹籍,失蹤者是張志新(化名)和其女友劉穎呢铆,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蹲缠,經(jīng)...
    沈念sama閱讀 44,318評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡棺克,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,645評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吼砂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逆航。...
    茶點(diǎn)故事閱讀 38,777評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖渔肩,靈堂內(nèi)的尸體忽然破棺而出因俐,到底是詐尸還是另有隱情,我是刑警寧澤周偎,帶...
    沈念sama閱讀 34,470評(píng)論 4 333
  • 正文 年R本政府宣布抹剩,位于F島的核電站,受9級(jí)特大地震影響蓉坎,放射性物質(zhì)發(fā)生泄漏澳眷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,126評(píng)論 3 317
  • 文/蒙蒙 一蛉艾、第九天 我趴在偏房一處隱蔽的房頂上張望钳踊。 院中可真熱鬧,春花似錦勿侯、人聲如沸拓瞪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,861評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽祭埂。三九已至,卻和暖如春兵钮,著一層夾襖步出監(jiān)牢的瞬間蛆橡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,095評(píng)論 1 267
  • 我被黑心中介騙來泰國(guó)打工掘譬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留泰演,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,589評(píng)論 2 362
  • 正文 我出身青樓屁药,卻偏偏與公主長(zhǎng)得像粥血,于是被迫代替她去往敵國(guó)和親柏锄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子酿箭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,687評(píng)論 2 351

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

  • 每個(gè)周日复亏,我都用簡(jiǎn)書來回顧過去一周我的時(shí)間管理情況。 這一周缭嫡,從以下幾個(gè)維度檢視: 健康缔御、學(xué)習(xí)、事業(yè)妇蛀、效能耕突、家庭、...
    建琪覺醒閱讀 628評(píng)論 0 51
  • 九點(diǎn)半出的萬達(dá)评架,這個(gè)時(shí)間點(diǎn)人們都忙著回家眷茁。 天橋下賣冰糖葫蘆的大爺準(zhǔn)備快點(diǎn)賣完最后的七八串,在那叫賣纵诞,“買一送一啊...
    祁羊閱讀 283評(píng)論 0 0