首先了解一下什么是線程颜说,再討論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)可以分為三種:
- 等待阻塞:運(yùn)行狀態(tài)中的線程執(zhí)行wait()方法涵紊,使本線程進(jìn)入到等待阻塞狀態(tài);
- 同步阻塞:線程在獲取synchronized同步鎖失斸C(因?yàn)殒i被其它線程占用)摸柄,線程進(jìn)入同步阻塞狀態(tài);
- 其它阻塞:通過調(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()方法中斷線程有兩種情況
- 線程處于阻塞狀態(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)棍郎。
}
}
}
}
- 線程未處于阻塞狀態(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ò)誤。
參考鏈接: