并發(fā)編程基礎(chǔ)(下)

書接上文。上文主要講了下線程的基本概念赫悄,三種創(chuàng)建線程的方式與區(qū)別,還介紹了線程的狀態(tài)馏慨,線程通知和等待涩蜘,join等,本篇繼續(xù)介紹并發(fā)編程的基礎(chǔ)知識熏纯。

sleep

當一個執(zhí)行的線程調(diào)用了Thread的sleep方法同诫,調(diào)用線程會暫時讓出指定時間的執(zhí)行權(quán),在這期間不參與CPU的調(diào)度樟澜,不占用CPU误窖,但是不會釋放該線程鎖持有的監(jiān)視器鎖。指定的時間到了后秩贰,該線程會回到就緒的狀態(tài)霹俺,再次等待分配CPU資源,然后再次執(zhí)行毒费。

我們有時會看到sleep(1)丙唧,甚至還有sleep(0)這種寫法,肯定會覺得非常奇怪觅玻,特別是sleep(0)想际,睡0秒鐘,有意義嗎溪厘?其實是有的胡本,sleep(1),sleep(0)的意義就在于告訴操作系統(tǒng)立刻觸發(fā)一次CPU競爭畸悬。

讓我們來看看正在sleep的進程被中斷了侧甫,會發(fā)生什么事情:

class MySleepTask implements Runnable{
    @Override
    public void run() {
        System.out.println("MyTask1");
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            System.out.println("中斷");
            e.printStackTrace();
        }
        System.out.println("MyTask2");
    }
}

public class Sleep {
    public static void main(String[] args) {
        MySleepTask mySleepTask=new MySleepTask();
        Thread thread=new Thread(mySleepTask);
        thread.start();
        thread.interrupt();
    }
}

運行結(jié)果:

MyTask1
中斷
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at java.lang.Thread.sleep(Thread.java:340)
    at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
    at com.codebear.MySleepTask.run(Sleep.java:10)
    at java.lang.Thread.run(Thread.java:748)
MyTask2

yield

我們知道線程是以時間片的機制來占用CPU資源并運行的,正常情況下蹋宦,一個線程只有把分配給自己的時間片用完之后披粟,線程調(diào)度器才會進行下一輪的線程調(diào)度,當執(zhí)行了Thread的yield后冷冗,就告訴操作系統(tǒng)“我不需要CPU了守屉,你現(xiàn)在就可以進行下一輪的線程調(diào)度了 ”,但是操作系統(tǒng)可以忽略這個暗示贾惦,也有可能下一輪還是把時間片分配給了這個線程胸梆。

我們來寫一個例子加深下印象:

class MyYieldTask implements Runnable {
    @Override
    public void run() {
        for (int i = 10; i > 0; i--) {
            System.out.println("我是" + Thread.currentThread().getName() + "敦捧,我分配到了時間片");
        }
    }
}


public class MyYield {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyYieldTask());
        thread1.start();

        Thread thread2 = new Thread(new MyYieldTask());
        thread2.start();
    }
}

運行結(jié)果:


image.png

當然由于線程的特性须板,所以每次運行結(jié)果可能都不太相同碰镜,但是當我們運行多次后,會發(fā)現(xiàn)絕大多數(shù)的時候习瑰,兩個線程的打印都是比較平均的绪颖,我用完時間片了,你用甜奄,你用完了時間片了柠横,我再用。

當我們調(diào)用yield后:

class MyYieldTask implements Runnable {
    @Override
    public void run() {
        for (int i = 10; i > 0; i--) {
            System.out.println("我是" + Thread.currentThread().getName() + "课兄,我分配到了時間片");
            Thread.yield();
        }
    }
}


public class MyYield {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyYieldTask());
        thread1.start();

        Thread thread2 = new Thread(new MyYieldTask());
        thread2.start();
    }
}

運行結(jié)果:


image.png

當然在一般情況下牍氛,可能永遠也不會用到y(tǒng)ield,但是還是要對這個方法有一定的了解烟阐。

sleep 和 yield 區(qū)別

當線程調(diào)用sleep后搬俊,會阻塞當前線程指定的時間,在這段時間內(nèi)蜒茄,線程調(diào)度器不會調(diào)用此線程唉擂,當指定的時間結(jié)束后,該線程的狀態(tài)為“就緒”檀葛,等待分配CPU資源玩祟。
當線程調(diào)用yield后,不會阻塞當前線程屿聋,只是讓出時間片空扎,回到“就緒”的狀態(tài),等待分配CPU資源润讥。

死鎖

死鎖是指多個線程在執(zhí)行的過程中勺卢,因為爭奪資源而造成的相互等待的現(xiàn)象,而且無法打破這個“僵局”象对。

死鎖的四個必要條件:

  • 互斥:指線程對于已經(jīng)獲取到的資源進行排他性使用黑忱,即該資源只能被一個線程占有,如果還有其他線程也想占有勒魔,只能等待甫煞,直到占有資源的線程釋放該資源。
  • 請求并持有:指一個線程已經(jīng)占有了一個資源冠绢,但是還想占有其他的資源抚吠,但是其他資源已經(jīng)被其他線程占有了,所以當前線程只能等待弟胀,等待的同時并不釋放自己已經(jīng)擁有的資源楷力。
  • 不可剝奪:當一個線程獲取資源后喊式,不能被其他線程占有,只有在自己使用完畢后自己釋放資源萧朝。
  • 環(huán)路等待:即 T1線程正在等待T2占有的資源岔留,T2線程正在等待T3線程占有的資源,T3線程又在等待T1線程占有的資源检柬。

要想打破“死鎖”僵局献联,只需要破壞以上四個條件中的任意一個,但是程序員可以干預(yù)的只有“請求并持有”何址,“環(huán)路等待”兩個條件里逆,其余兩個條件是鎖的特性,程序員是無法干預(yù)的用爪。

聰明的你原押,一定看出來了,所謂“死鎖”就是“悲觀鎖”造成的偎血,相對于“死鎖”诸衔,還有一個“活鎖”,就是“樂觀鎖”造成的烁巫。

守護線程與用戶線程

Java中的線程分為兩類署隘,分別為 用戶線程和守護線程。在JVM啟動時亚隙,會調(diào)用main函數(shù)磁餐,這個就是用戶線程,JVM內(nèi)部還會啟動一些守護線程阿弃,比如垃圾回收線程诊霹。那么守護線程和用戶線程到底有什么區(qū)別呢?當最后一個用戶線程結(jié)束后渣淳,JVM就自動退出了脾还,而不管當前是否有守護線程還在運行。
如何創(chuàng)建一個守護線程呢入愧?

public class Daemon {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
        });
        thread.setDaemon(true);
        thread.start();
    }
}

只需要設(shè)置線程的daemon為true就可以鄙漏。
下面來演示下用戶線程與守護線程的區(qū)別:

public class Daemon {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (true){}
        });

        thread.start();
    }
}

當我們運行后,可以發(fā)現(xiàn)程序一直沒有退出:


image.png

因為這是用戶線程棺蛛,只要有一個用戶線程還沒結(jié)束怔蚌,程序就不會退出。

再來看看守護線程:

public class Daemon {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (true){}
        });
        thread.setDaemon(true);
        thread.start();
    }
}

當我們運行后旁赊,發(fā)現(xiàn)程序立刻就停止了:


image.png

因為這是守護線程桦踊,當用戶線程結(jié)束后,不管有沒有守護線程還在運行终畅,程序都會退出籍胯。

線程中斷

之所以把線程中斷放在后面竟闪,是因為它是并發(fā)編程基礎(chǔ)中最難以理解的一個,當然這也與不經(jīng)常使用有關(guān)≌壤牵現(xiàn)在就讓我們好好看看線程中斷炼蛤。
Thread提供了stop方法,用來停止當前線程本刽,但是已經(jīng)被標記為過期鲸湃,應(yīng)該用線程中斷方法來代替stop方法赠涮。

interrupt

中斷線程子寓。當線程A運行(非阻塞)時,線程B可以調(diào)用線程A的interrupt方法來設(shè)置線程A的中斷標記為true笋除,這里要特別注意斜友,調(diào)用interrupt方法并不會真的去中斷線程,只是設(shè)置了中斷標記為true垃它,線程A還是活的好好的鲜屏。如果線程A被阻塞了,比如調(diào)用了sleep国拇、wait洛史、join,線程A會在調(diào)用這些方法的地方拋出“InterruptedException”酱吝。
我們來做個試驗也殖,證明下interrupt方法不會中斷正在運行的線程:

class InterruptTask implements Runnable {
    @Override
    public void run() {
        CopyOnWriteArrayList copyOnWriteArrayList = new CopyOnWriteArrayList();
        try {
            long start = System.currentTimeMillis();
            for (int i = 0; i < 150000; i++) {
                copyOnWriteArrayList.add(i);
            }
            System.out.println("結(jié)束了,時間是" + (System.currentTimeMillis() - start));
            System.out.println(Thread.currentThread().isInterrupted());
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

public class InterruptTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new InterruptTask());
        thread1.start();
        thread1.interrupt();
    }
}

運行結(jié)果:

結(jié)束了,時間是7643
true

在子線程中,我們通過一個循環(huán)往copyOnWriteArrayList里面添加數(shù)據(jù)來模擬一個耗時操作务热。這里要特別要注意忆嗜,一般來說,我們模擬耗時操作都是用sleep方法崎岂,但是這里不能用sleep方法捆毫,因為調(diào)用sleep方法會讓當前線程阻塞,而現(xiàn)在是要讓線程處于運行的狀態(tài)冲甘。我們可以很清楚的看到绩卤,雖然子線程剛運行,就被interrupt了江醇,但是卻沒有拋出任何異常濒憋,也沒有讓子線程終止,子線程還是活的好好的嫁审,只是最后打印出的“中斷標記”為true跋炕。

如果沒有調(diào)用interrupt方法,中斷標記為false:

class InterruptTask implements Runnable {
    @Override
    public void run() {
        CopyOnWriteArrayList copyOnWriteArrayList = new CopyOnWriteArrayList();
        try {
            long start = System.currentTimeMillis();
            for (int i = 0; i < 500; i++) {
                copyOnWriteArrayList.add(i);
            }
            System.out.println("結(jié)束了,時間是" + (System.currentTimeMillis() - start));
            System.out.println(Thread.currentThread().isInterrupted());
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

public class InterruptTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new InterruptTask());
        thread1.start();
    }
}

運行結(jié)果:

結(jié)束了,時間是1
false

在介紹sleep律适,wait辐烂,join方法的時候遏插,大家已經(jīng)看到了,如果中斷調(diào)用這些方法而被阻塞的線程會拋出異常纠修,這里就不再演示了胳嘲,但是還有一點需要注意,當我們catch住InterruptedException異常后扣草,“中斷標記”會被重置為false了牛,我們繼續(xù)做實驗:

class InterruptTask implements Runnable {
    @Override
    public void run() {
        CopyOnWriteArrayList copyOnWriteArrayList = new CopyOnWriteArrayList();
        try {
            long start = System.currentTimeMillis();
            TimeUnit.SECONDS.sleep(3);
            System.out.println("結(jié)束了,時間是" + (System.currentTimeMillis() - start));
        } catch (Exception ex) {
            System.out.println(Thread.currentThread().isInterrupted());
            ex.printStackTrace();
        }
    }
}

public class InterruptTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new InterruptTask());
        thread1.start();
        thread1.interrupt();
    }
}

運行結(jié)果:

false
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at java.lang.Thread.sleep(Thread.java:340)
    at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
    at com.codebear.InterruptTask.run(InterruptTest.java:20)
    at java.lang.Thread.run(Thread.java:748)

可以很清楚的看到,“中斷標記”被重置為false了辰妙。

還有一個問題鹰祸,大家可以思考下,代碼的本意是當前線程被中斷后退出死循環(huán)密浑,這段代碼有問題嗎蛙婴?

Thread th = Thread.currentThread();
while(true) {
  if(th.isInterrupted()) {
    break;
  }
 
  try {
    Thread.sleep(100);
  }catch (InterruptedException e){
    e.printStackTrace();
  }
}

本題來自 極客時間 王寶令 老師的 《Java并發(fā)編程實戰(zhàn)》

代碼是有問題的,因為catch住異常后尔破,會把“中斷標記”重置街图。如果正好在sleep的時候,線程被中斷了懒构,又重置了“中斷標記”餐济,那么下一次循環(huán),檢測中斷標記為false胆剧,就無法退出死循環(huán)了絮姆。

isInterrupted

這個方法在上面已經(jīng)出現(xiàn)過了,就是 獲取對象線程的“中斷標記”赞赖。

interrupted

獲取當前線程的“中斷標記”滚朵,如果發(fā)現(xiàn)當前線程被中斷,會重置中斷標記為false前域,該方法是static方法辕近,通過Thread類直接調(diào)用。

并發(fā)編程基礎(chǔ)到這里就結(jié)束了匿垄,可以看到內(nèi)容還是相當多的移宅,雖說是基礎(chǔ),但是每一個知識點椿疗,如果要深究的話漏峰,都可以牽扯到“操作系統(tǒng)”,所以只有深入到了“操作系統(tǒng)”届榄,才可以說真的懂了浅乔,現(xiàn)在還是僅僅停留在Java的層面,唉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末靖苇,一起剝皮案震驚了整個濱河市席噩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贤壁,老刑警劉巖悼枢,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異脾拆,居然都是意外死亡馒索,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門名船,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绰上,“玉大人,你說我怎么就攤上這事包帚∮嫫冢” “怎么了运吓?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵渴邦,是天一觀的道長。 經(jīng)常有香客問我拘哨,道長谋梭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任倦青,我火速辦了婚禮瓮床,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘产镐。我一直安慰自己隘庄,他們只是感情好,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布癣亚。 她就那樣靜靜地躺著丑掺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪述雾。 梳的紋絲不亂的頭發(fā)上街州,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音玻孟,去河邊找鬼唆缴。 笑死,一個胖子當著我的面吹牛黍翎,可吹牛的內(nèi)容都是我干的面徽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼匣掸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起永淌,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤竣付,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后坊萝,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年造挽,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弄痹。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡饭入,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出肛真,到底是詐尸還是另有隱情谐丢,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布蚓让,位于F島的核電站乾忱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏历极。R本人自食惡果不足惜窄瘟,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望趟卸。 院中可真熱鬧蹄葱,春花似錦、人聲如沸锄列。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽邻邮。三九已至竣况,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間饶囚,已是汗流浹背帕翻。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留萝风,地道東北人嘀掸。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像规惰,于是被迫代替她去往敵國和親睬塌。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354