Java線程

首先了解一下什么是線程颜说,再討論Java中線程的創(chuàng)建和結(jié)束购岗。

一、線程和進(jìn)程

1.1 線程和進(jìn)程的區(qū)別

線程和進(jìn)程都是一個(gè)時(shí)間段的描述门粪,是CPU工作時(shí)間段的描述喊积。

進(jìn)程就是包含上下文切換的程序執(zhí)行時(shí)間總和 = CPU加載上下文+CPU執(zhí)行+CPU保存上下文

線程是共享了進(jìn)程的上下文環(huán)境,的更為細(xì)小的CPU時(shí)間段

cr具體參考:進(jìn)程和線程的區(qū)別

簡而言之玄妈,當(dāng)我們啟動(dòng)一個(gè)應(yīng)用時(shí)乾吻,要實(shí)現(xiàn)這個(gè)應(yīng)用的功能,必然有多條邏輯支持措近,在具體執(zhí)行一個(gè)程序的時(shí)候溶弟,會(huì)先執(zhí)行程序的a小段,再執(zhí)行程序的b小段等等瞭郑,a辜御、b等就是線程。
在《Java編程思想》中屈张,用了這樣一句話來描述:一個(gè)線程就是在進(jìn)程中的一個(gè)單一的順序控制流擒权。

1.2 線程的狀態(tài)

下圖展示了Java中線程的生命周期袱巨。

線程的生命周期

可以看出,Java線程具有五種基本狀態(tài):New碳抄,Runnable愉老,Running,Blocked剖效,Dead嫉入。

  • 新建狀態(tài)(New):當(dāng)線程對(duì)象創(chuàng)建后,即進(jìn)入了新建狀態(tài)璧尸,如:Thread t =new MyThread()咒林。(但此時(shí)只是創(chuàng)建了線程對(duì)象,并沒有啟動(dòng)爷光,處于創(chuàng)建狀態(tài)的線程僅僅分配了內(nèi)存空間垫竞,屬于生命周期的初始狀態(tài)。)

  • 就緒狀態(tài)(Runnable):當(dāng)調(diào)用該線程對(duì)象的start()方法蛀序,線程就進(jìn)入了就緒狀態(tài)欢瞪。(但是并沒有執(zhí)行,只是表示已經(jīng)做好了準(zhǔn)備徐裸,等待CPU調(diào)度遣鼓。處于就緒狀態(tài)的線程具備了除CPU之外運(yùn)行所需的所有資源。)

  • 運(yùn)行狀態(tài)(Running):當(dāng)線程獲取CPU倦逐,執(zhí)行程序片段譬正,此時(shí)線程才得以真正執(zhí)行宫补,進(jìn)入了運(yùn)行狀態(tài)檬姥。(就緒狀態(tài)是進(jìn)入到運(yùn)行狀態(tài)的唯一入口,即粉怕,線程想進(jìn)入運(yùn)行狀態(tài)健民,必須先處于就緒狀態(tài)。)

  • 阻塞狀態(tài)(Blocked):處于運(yùn)行狀態(tài)中的線程由于某種原因贫贝,暫時(shí)放棄對(duì)CPU的使用權(quán)秉犹,停止執(zhí)行,此時(shí)線程進(jìn)入阻塞狀態(tài)稚晚。( 阻塞狀態(tài)與就緒狀態(tài)不同崇堵,就緒狀態(tài)線程只是因?yàn)槿鄙貱PU不能繼續(xù)執(zhí)行,而阻塞狀態(tài)是由于各種原因而不能繼續(xù)執(zhí)行客燕,不僅僅是缺少CPU鸳劳。引起阻塞的原因解除后,線程再次轉(zhuǎn)為就緒狀態(tài)也搓,分配CPU運(yùn)行 赏廓。)
    阻塞狀態(tài)可以分為三種:

  1. 等待阻塞:運(yùn)行狀態(tài)中的線程執(zhí)行wait()方法涵紊,使本線程進(jìn)入到等待阻塞狀態(tài);
  2. 同步阻塞:線程在獲取synchronized同步鎖失斸C(因?yàn)殒i被其它線程占用)摸柄,線程進(jìn)入同步阻塞狀態(tài);
  3. 其它阻塞:通過調(diào)用線程的sleep()或join()或發(fā)出了I/O請(qǐng)求時(shí)既忆,線程會(huì)進(jìn)入到阻塞狀態(tài)驱负。當(dāng)sleep()狀態(tài)超時(shí)、join()等待線程終止或者超時(shí)患雇、或者I/O處理完畢時(shí)电媳,線程重新轉(zhuǎn)為就緒轉(zhuǎn)臺(tái)。
  • 死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常推出了run()方法庆亡,該線程結(jié)束生命周期匾乓。

二、線程的創(chuàng)建

Android實(shí)現(xiàn)子線程主要用兩種方式:1又谋、繼承Thread類拼缝;2、實(shí)現(xiàn)Runnable接口彰亥。

2.1 Thread類

繼承Thread類咧七,并重寫run()方法。
啟動(dòng)線程(Thread類的start()方法)

e.g.
MyThread類繼承了Thread類任斋,并且在run()方法中執(zhí)行了網(wǎng)絡(luò)請(qǐng)求继阻。但是子線程不能對(duì)UI做修改,所以講數(shù)據(jù)通過Handler發(fā)送給主線程废酷,由主線程修改UI瘟檩。

String url = "http://192.168.1.114:8080/json/get_data.json";  
private TextView tv;  
StringBuffer buffer = new StringBuffer();  
private Handler MyHandle = new Handler() {  
    public void handleMessage(android.os.Message msg) {  
        String json = (String) msg.obj;  
        List<Person> list = parseJsonData(json);  
        for (Person person : list) {  
            buffer.append(person.toString());  
        }  
        tv.setText(buffer.toString());  
    };  
};  
  
@Override  
protected void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    setContentView(R.layout.activity_main);  
    tv = (TextView) findViewById(R.id.tv_msg);  
    findViewById(R.id.btn_getdata).setOnClickListener(  
            new OnClickListener() {  
  
                @Override  
                public void onClick(View v) {  
                    // TODO Auto-generated method stub  
                    new MyThread().start();  
                }  
            });  
}  
  
class MyThread extends Thread {  
    @Override  
    public void run() {  
        String jsonStr = NetWorkUtils.getData1(url);  
        if (!TextUtils.isEmpty(jsonStr)) {  
            Log.e("jsonStr", jsonStr);  
            Message message = new Message();  
            message.obj = jsonStr;  
            MyHandle.sendMessage(message);  
        }  
    }  
}  
  
  
**  
 * 使用gson解析  
 * @param jsonStr  
 */  
private List<Person> parseJsonData(String jsonStr) {  
    Gson gson = new Gson();  
    List<Person> list = gson.fromJson(jsonStr,  
            new TypeToken<List<Person>>() {  
            }.getType());  
    return list;  
}  

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

線程可以驅(qū)動(dòng)任務(wù),因此你需要一種描述任務(wù)的方式澈蟆,這可以有Runnable接口來提供墨辛。要想定義任務(wù),只需實(shí)現(xiàn)Runnable接口并編寫run()方法趴俘,使得該任務(wù)可以執(zhí)行你的命令睹簇。

Runnable只是一個(gè)接口,從Runnable導(dǎo)出一個(gè)類時(shí)寥闪,它必須具有run()方法太惠,但是這個(gè)方法很普通,不會(huì)產(chǎn)生任何內(nèi)在的線程能力疲憋。所以即使實(shí)現(xiàn)了Runnable凿渊,也無法啟動(dòng)線程,必須依托其它的類。(通常使用Thread類來啟動(dòng))
所以只是實(shí)現(xiàn)Runnable接口并不能啟動(dòng)或者說實(shí)現(xiàn)一個(gè)線程嗽元。

String url = "http://192.168.1.114:8080/json/get_data.json";  
private TextView tv;  
StringBuffer buffer = new StringBuffer();  
private Handler MyHandle = new Handler() {  
    public void handleMessage(android.os.Message msg) {  
        String json = (String) msg.obj;  
        List<Person> list = parseJsonData(json);  
        for (Person person : list) {  
            buffer.append(person.toString());  
        }  
        tv.setText(buffer.toString());  
    };  
};  
  
@Override  
protected void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    setContentView(R.layout.activity_main);  
    tv = (TextView) findViewById(R.id.tv_msg);  
    findViewById(R.id.btn_getdata).setOnClickListener(  
            new OnClickListener() {  
  
                @Override  
                public void onClick(View v) {  
                    // TODO Auto-generated method stub  
                    new Thread(new MyRunnable()).start();  
                }  
            });  
}  
  
class MyRunnable implements Runnable {  
  
    @Override  
    public void run() {  
         String jsonStr = NetWorkUtils.getData1(url);  
        if (!TextUtils.isEmpty(jsonStr)) {  
            Log.e("jsonStr", jsonStr);  
            Message message = new Message();  
            message.obj = jsonStr;  
            MyHandle.sendMessage(message);  
        }  
    }  
}  
  
  
**  
 * 使用gson解析  
 * @param jsonStr  
 */  
private List<Person> parseJsonData(String jsonStr) {  
    Gson gson = new Gson();  
    List<Person> list = gson.fromJson(jsonStr,  
            new TypeToken<List<Person>>() {  
            }.getType());  
    return list;  
}  

三敛纲、線程的銷毀

有三種方法可以結(jié)束線程

  • 設(shè)置退出標(biāo)志,使線程正常退出剂癌,即當(dāng)run()方法完成后線程終止淤翔。
  • 使用interrupt()方法中斷線程
  • 使用stop方法強(qiáng)行終止線程(不推薦使用,Thread.stop, Thread.suspend, Thread.resume 和Runtime.runFinalizersOnExit 這些終止線程運(yùn)行的方法已經(jīng)被廢棄佩谷,使用它們是極端不安全的E宰场)

3.1 使用退出標(biāo)志終止線程

使用一個(gè)變量來控制循環(huán)

public class ThreadSafe extends Thread {
    public volatile boolean exit = false; 
        public void run() { 
        while (!exit){
            //do something
        }
    } 
}

代碼中定義了一個(gè)退出標(biāo)志exit,當(dāng)exit為true時(shí)谐檀,while循環(huán)退出抡谐,exit的默認(rèn)值為false。在定義exit時(shí)桐猬,使用了一個(gè)Java關(guān)鍵字volatile麦撵,這個(gè)關(guān)鍵字的目的是使exit同步,也就是說在同一時(shí)刻只能由一個(gè)線程來修改exit的值.

3.2 使用interrupr()方法中斷當(dāng)前線程

使用interrupt()方法中斷線程有兩種情況

  1. 線程處于阻塞狀態(tài)
    在開篇中提到了線程會(huì)在什么情況下處于阻塞狀態(tài)溃肪。當(dāng)調(diào)用線程的interrupt()方法時(shí)免胃,會(huì)拋出InterruptException異常。阻塞中的那個(gè)方法拋出這個(gè)異常惫撰,通過代碼捕獲該異常羔沙,然后break跳出循環(huán)狀態(tài),從而有機(jī)會(huì)結(jié)束這個(gè)線程的執(zhí)行厨钻。
    通常很多人認(rèn)為只要調(diào)用interrupt方法線程就會(huì)結(jié)束扼雏,實(shí)際上是錯(cuò)的, 一定要先捕獲InterruptedException異常之后通過break來跳出循環(huán)夯膀,才能正常結(jié)束run方法诗充。
    e.g.
public class ThreadSafe extends Thread {
    public void run() { 
        while (true){
            try{
                    Thread.sleep(5*1000);//阻塞5妙
                }catch(InterruptedException e){
                    e.printStackTrace();
                    break;//捕獲到異常之后,執(zhí)行break跳出循環(huán)棍郎。
                }
        }
    } 
}
  1. 線程未處于阻塞狀態(tài)
    使用isInterrupted()判斷線程的中斷標(biāo)志來退出循環(huán)其障,當(dāng)使用interrupt()方法時(shí),中斷標(biāo)志就會(huì)置true涂佃,和使用自定義的標(biāo)志來控制循環(huán)是一樣的道理。
    e.g.
public class ThreadSafe extends Thread {
    public void run() { 
        while (!isInterrupted()){
            //do something, but no throw InterruptedException
        }
    } 
}

為什么要區(qū)分進(jìn)入阻塞狀態(tài)和和非阻塞狀態(tài)兩種情況了蜈敢,是因?yàn)?em>當(dāng)阻塞狀態(tài)時(shí)辜荠,如果有interrupt()發(fā)生,系統(tǒng)除了會(huì)拋出InterruptedException異常外抓狭,還會(huì)調(diào)用interrupted()函數(shù)伯病,調(diào)用時(shí)能獲取到中斷狀態(tài)是true的狀態(tài),調(diào)用完之后會(huì)復(fù)位中斷狀態(tài)為false,所以異常拋出之后通過isInterrupted()是獲取不到中斷狀態(tài)是true的狀態(tài)午笛,從而不能退出循環(huán)惭蟋,因此在線程未進(jìn)入阻塞的代碼段時(shí)是可以通過isInterrupted()來判斷中斷是否發(fā)生來控制循環(huán),在進(jìn)入阻塞狀態(tài)后要通過捕獲異常來退出循環(huán)药磺。因此使用interrupt()來退出線程的最好的方式應(yīng)該是兩種情況都要考慮告组。
e.g.

public class ThreadSafe extends Thread {
    public void run() { 
        while (!isInterrupted()){ //非阻塞過程中通過判斷中斷標(biāo)志來退出
            try{
                Thread.sleep(5*1000);//阻塞過程捕獲中斷異常來退出
            }catch(InterruptedException e){
                e.printStackTrace();
                break;//捕獲到異常之后,執(zhí)行break跳出循環(huán)癌佩。
            }
        }
    } 
}

3.3 使用stop方法終止線程<不推薦>

程序中可以直接使用thread.stop()來強(qiáng)行終止線程木缝,但stop方法很危險(xiǎn)。
原因在于:thread.stop()調(diào)用之后围辙,創(chuàng)建子線程的線程就會(huì)拋出ThreadDeatherror的錯(cuò)誤我碟,并且會(huì)釋放子線程所持有的線程鎖。一般任何進(jìn)行加鎖的代碼塊姚建,都是為了保護(hù)數(shù)據(jù)的一致性矫俺,如果在調(diào)用thread.stop()后導(dǎo)致了該線程所持有的所有鎖突然釋放,那么被保護(hù)的數(shù)據(jù)可能呈現(xiàn)不一致性掸冤。其它線程在使用這些被破壞的數(shù)據(jù)時(shí)恳守,可能導(dǎo)致一些很奇怪的應(yīng)用程序錯(cuò)誤。

參考鏈接:

  1. Java總結(jié)篇系列:Java多線程(一)
  2. 你不知道的Runnable接口贩虾,深度解析Runnable接口
  3. Android(線程一)線程
  4. Java結(jié)束線程的三種方法
  5. Android正確關(guān)閉線程
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末催烘,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子缎罢,更是在濱河造成了極大的恐慌伊群,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件策精,死亡現(xiàn)場(chǎng)離奇詭異舰始,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)咽袜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門丸卷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人询刹,你說我怎么就攤上這事谜嫉。” “怎么了凹联?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵沐兰,是天一觀的道長。 經(jīng)常有香客問我蔽挠,道長住闯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮比原,結(jié)果婚禮上插佛,老公的妹妹穿的比我還像新娘。我一直安慰自己量窘,他們只是感情好雇寇,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著绑改,像睡著了一般谢床。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上厘线,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天识腿,我揣著相機(jī)與錄音,去河邊找鬼造壮。 笑死渡讼,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的耳璧。 我是一名探鬼主播成箫,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼旨枯!你這毒婦竟也來了蹬昌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤攀隔,失蹤者是張志新(化名)和其女友劉穎皂贩,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體昆汹,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡明刷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了满粗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辈末。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖映皆,靈堂內(nèi)的尸體忽然破棺而出挤聘,到底是詐尸還是另有隱情,我是刑警寧澤劫扒,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布檬洞,位于F島的核電站,受9級(jí)特大地震影響沟饥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一贤旷、第九天 我趴在偏房一處隱蔽的房頂上張望广料。 院中可真熱鬧,春花似錦幼驶、人聲如沸艾杏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽购桑。三九已至,卻和暖如春氏淑,著一層夾襖步出監(jiān)牢的瞬間勃蜘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國打工假残, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缭贡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓辉懒,卻偏偏與公主長得像阳惹,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子眶俩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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

  • 0 前言 當(dāng)線程被創(chuàng)建并啟動(dòng)以后莹汤,它既不是一啟動(dòng)就進(jìn)入了執(zhí)行狀態(tài),也不是一直處于執(zhí)行狀態(tài)颠印。在線程的生命周期中纲岭,它要...
    七寸知架構(gòu)閱讀 5,195評(píng)論 2 63
  • 【JAVA 線程】 線程 進(jìn)程:是一個(gè)正在執(zhí)行中的程序。每一個(gè)進(jìn)程執(zhí)行都有一個(gè)執(zhí)行順序嗽仪。該順序是一個(gè)執(zhí)行路徑荒勇,或者...
    Rtia閱讀 2,768評(píng)論 2 20
  • 下面是我自己收集整理的Java線程相關(guān)的面試題,可以用它來好好準(zhǔn)備面試闻坚。 參考文檔:-《Java核心技術(shù) 卷一》-...
    阿呆變Geek閱讀 14,839評(píng)論 14 507
  • Java中的線程(多線程)沽翔,本篇主要講一下線程的概念和基本操作以及各個(gè)方法的用法等;首先在了解線程前我們必須應(yīng)該知...
    星星_點(diǎn)燈閱讀 373評(píng)論 0 0
  • 注:本文屬代發(fā)文,作者:Fiona菲奧娜214 & & 眾人的驚呼聲中雳殊,優(yōu)一郎先是愣怔橘沥,再是捂著頭驚叫,“啊啊啊啊...
    榳榳淯栗淯榳榳閱讀 7,624評(píng)論 0 6