現(xiàn)在的操作系統(tǒng)是多任務(wù)操作系統(tǒng)。多線程是實(shí)現(xiàn)多任務(wù)的一種方式。
進(jìn)程與線程
進(jìn)程是指一個(gè)內(nèi)存中運(yùn)行的應(yīng)用程序,每個(gè)進(jìn)程都有自己獨(dú)立的一塊內(nèi)存空間,一個(gè)進(jìn)程中可以啟動(dòng)多個(gè)線程。比如在Windows系統(tǒng)中,一個(gè)運(yùn)行的exe就是一個(gè)進(jìn)程。
線程是指進(jìn)程中的一個(gè)執(zhí)行流程,一個(gè)進(jìn)程中可以運(yùn)行多個(gè)線程。比如Java.exe進(jìn)程中可以運(yùn)行很多線程。線程總是屬于某個(gè)進(jìn)程,進(jìn)程中的多個(gè)線程共享進(jìn)程的內(nèi)存。
線程分為兩類:用戶線程和守候線程。
當(dāng)所有用戶線程執(zhí)行完畢后,JVM自動(dòng)關(guān)閉。但是守候線程卻不獨(dú)立與JVM,守候線程一般是有操作系統(tǒng)或用戶自己創(chuàng)建的。
Java中線程生命周期

Java線程具有五中基本狀態(tài)
新建狀態(tài)(New):新建線程對(duì)象就進(jìn)入了新建狀態(tài),就像new一個(gè)Thread線程對(duì)象
就緒狀態(tài)(Runnable):當(dāng)調(diào)用線程對(duì)象的start()方法,線程即進(jìn)入就緒狀態(tài)。處于就緒狀態(tài)的線程,只是說(shuō)明此線程已經(jīng)做好了準(zhǔn)備,隨時(shí)等待CPU調(diào)度執(zhí)行。
運(yùn)行狀態(tài)(Running):當(dāng)CPU開(kāi)始調(diào)度處于就緒狀態(tài)的線程時(shí),此時(shí)線程才得以真正執(zhí)行,即進(jìn)入到運(yùn)行狀態(tài)。就緒狀態(tài)是進(jìn)入到運(yùn)行狀態(tài)的唯一入口,線程想進(jìn)入運(yùn)行狀態(tài)執(zhí)行,首先必須處于就緒狀態(tài)中
阻塞狀態(tài)(Blocked):處于運(yùn)行狀態(tài)中的線程由于某種原因,暫時(shí)放棄對(duì)CPU的使用權(quán),停止執(zhí)行,此時(shí)進(jìn)入阻塞狀態(tài),直到其進(jìn)入到就緒狀態(tài),才有機(jī)會(huì)再次被CPU調(diào)用以進(jìn)入到運(yùn)行狀態(tài)。
根據(jù)阻塞產(chǎn)生的原因不同,阻塞狀態(tài)又可以分為三種:
1.等待阻塞:運(yùn)行狀態(tài)中的線程執(zhí)行wait()方法,使本線程進(jìn)入到等待阻塞狀態(tài);
2.同步阻塞 -- 線程在獲取synchronized同步鎖失敗(因?yàn)殒i被其它線程所占用),它會(huì)進(jìn)入同步阻塞狀態(tài)
3.其他阻塞 -- 通過(guò)調(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)入就緒狀態(tài)。
死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法,該線程結(jié)束生命周期。
Java編寫(xiě)程序都運(yùn)行在在Java虛擬機(jī)(JVM)中,在JVM的內(nèi)部,程序的多任務(wù)是通過(guò)線程來(lái)實(shí)現(xiàn)的。每用Java命令啟動(dòng)一個(gè)Java應(yīng)用程序,就會(huì)啟動(dòng)一個(gè)JVM進(jìn)程。在同一個(gè)JVM進(jìn)程中,有且只有一個(gè)進(jìn)程,就是它自己。在這個(gè)JVM環(huán)境中,所有程序代碼的運(yùn)行都是以線程來(lái)運(yùn)行。
在Java中創(chuàng)建線程
在創(chuàng)建實(shí)例前先創(chuàng)建類Singleton(線程不安全)
public class Singleton {
private Singleton() {}
private static Singleton single=null;
//靜態(tài)工廠方法
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
1.繼承Thread類創(chuàng)建線程類
public class Single extends Thread{
@Override
public void run() {
Singleton singleton=Singleton.getInstance();
System.out.println(singleton);
}
}
創(chuàng)建一個(gè)類繼承擴(kuò)展Thread類,在這子類中重寫(xiě)run(),在run()內(nèi)寫(xiě)線程任務(wù)代碼
在main方法中測(cè)試
public class DoTest {
public static void main(String[] args) {
Single single1 = new Single();
Single single2 = new Single();
Thread t1 = new Thread(single1);
Thread t2 = new Thread(single2);
t1.start();
t2.start();
}
}
創(chuàng)建該子類實(shí)例,即是創(chuàng)建了一個(gè)線程實(shí)例并調(diào)用該實(shí)例的start方法來(lái)啟動(dòng)該線程
結(jié)果:

2.實(shí)現(xiàn)Runnable接口創(chuàng)建線程類
public class Single implements Runnable{
@Override
public void run() {
Singleton singleton=Singleton.getInstance();
System.out.println(singleton);
}
}
實(shí)現(xiàn)Runnable接口構(gòu)造線程的方法是: 要在一個(gè)擴(kuò)展了Runnable接口的類中實(shí)現(xiàn)接口中的抽象方法run()
在main方法中測(cè)試
public class DoTest {
public static void main(String[] args) {
Single single=new Single();
new Thread(single).start();
new Thread(single).start();
}
}
Thread類的構(gòu)造方法要求將一個(gè)Runable接口類型的對(duì)象作為參數(shù),這個(gè)就將這個(gè)接口的run()與所聲明的Thread對(duì)象綁定在一起,也就可以用這個(gè)對(duì)象的start()和sleep()來(lái)控制這個(gè)線程。
結(jié)果:

注:對(duì)于線程的啟動(dòng)而言,都是調(diào)用線程對(duì)象的start()方法,不能對(duì)同一線程對(duì)象兩次調(diào)用start()方法。第二次調(diào)用必然會(huì)拋出IllegalThreadStateException,這是一種運(yùn)行時(shí)異常,多次調(diào)用start()被認(rèn)為是編程錯(cuò)誤。
關(guān)于繼承Thread類與實(shí)現(xiàn)Runnable接口的區(qū)別
1.使用繼承Thread類實(shí)現(xiàn)多線程是多個(gè)線程分別完成自己的任務(wù),實(shí)現(xiàn)Runnable接口實(shí)現(xiàn)多線程是多個(gè)線程共同完成一個(gè)任務(wù)。其實(shí)在實(shí)現(xiàn)一個(gè)任務(wù)用多個(gè)線程來(lái)做也可以用繼承Thread類來(lái)實(shí)現(xiàn)只是比較麻煩,一般我們用實(shí)現(xiàn)Runnable接口來(lái)實(shí)現(xiàn),簡(jiǎn)潔明了。
2.資源共享:繼承Thread不適合資源共享。實(shí)現(xiàn)Runable接口的話很容易的實(shí)現(xiàn)資源共享。通過(guò)繼承Thread類,每個(gè)線程都有一個(gè)相關(guān)聯(lián)的唯一對(duì)象,而實(shí)現(xiàn)Runnable接口,多線程可以共享同一個(gè)Runnable實(shí)例。
3.Java單繼承性,使用繼承Thread有點(diǎn)局限,而實(shí)現(xiàn)Runable接口克服了單繼承的限制,用接口的方式將你的代碼和線程實(shí)現(xiàn)分離,更加清晰。
實(shí)現(xiàn)Runnable接口比繼承Thread類所具有的優(yōu)勢(shì):
1.適合多個(gè)相同的程序代碼的線程去處理同一個(gè)資源
2.可以避免Java中的單繼承的限制
3.增加程序的健壯性,代碼可以被多個(gè)線程共享,代碼和數(shù)據(jù)獨(dú)立
不過(guò)這兩種方式都有一個(gè)缺陷就是:在執(zhí)行完任務(wù)之后無(wú)法獲取執(zhí)行結(jié)果。如果需要獲取執(zhí)行結(jié)果,就必須通過(guò)共享變量或者使用線程通信的方式來(lái)達(dá)到效果,這樣使用起來(lái)就比較麻煩。
3.實(shí)現(xiàn)Callable接口創(chuàng)建線程類
Callable接口類似于 Runnable,Callable接口的任務(wù)線程能返回執(zhí)行結(jié)果并且其call()允許拋出異常,而Runnable接口的run()方法的異常只能在內(nèi)部消化,不能繼續(xù)上拋
Callable接口源碼
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
Callable接口位于java.util.concurrent包下,在里面聲明了一個(gè)call()
這是一個(gè)泛型接口,call()函數(shù)返回的類型就是傳遞進(jìn)來(lái)的V類型。
Future表示異步計(jì)算的結(jié)果,就是對(duì)于具體的Runnable或者Callable任務(wù)的執(zhí)行結(jié)果進(jìn)行取消、查詢是否完成、獲取結(jié)果。必要時(shí)可以通過(guò)get()獲取執(zhí)行結(jié)果,該方法會(huì)阻塞直到任務(wù)返回結(jié)果。
使用Callable+Future獲取執(zhí)行結(jié)果
public class Single implements Callable<String> {
@Override
public String call() throws Exception {
Singleton singleton=Singleton.getInstance();
System.out.println(singleton);
return "Callable+Future獲取執(zhí)行結(jié)果";
}
}
這里我簡(jiǎn)單返回一句話
在main方法中測(cè)試
public class DoTest {
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
Single single = new Single();
Future<String> single1 = executor.submit(single);
Future<String> single2 = executor.submit(single);
try {
System.out.println(single1.get());
System.out.println(single2.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}finally {
executor.shutdown();
}
}
}
Callable一般要配合線程池ExecutorService來(lái)使用,ExecutorService是Executor直接的擴(kuò)展接口,也是最常用的線程池接口,Executor的實(shí)現(xiàn)提供的一些方法可以返回一個(gè)Future對(duì)象, 通過(guò)它我們可以跟蹤到異步任務(wù)的執(zhí)行和停止。
測(cè)試結(jié)果

線程結(jié)果正常返回出來(lái)了
使用Callable+FutureTask獲取執(zhí)行結(jié)果
只需要修改main方法
public class DoTest {
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
Single single = new Single();
FutureTask<String> single1 = new FutureTask<String>(single);
FutureTask<String> single2 = new FutureTask<String>(single);
executor.submit(single1);
executor.submit(single2);
try {
System.out.println(single1.get());
System.out.println(single2.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}finally {
executor.shutdown();
}
}
}
測(cè)試結(jié)果

關(guān)于Future和FutureTask
FutureTask是Future接口的一個(gè)唯一實(shí)現(xiàn)類。
FutureTask實(shí)現(xiàn)了Runnable,因此它既可以通過(guò)Thread包裝來(lái)直接執(zhí)行,也可以提交給ExecuteService來(lái)執(zhí)行。
FutureTask實(shí)現(xiàn)了Futrue可以直接通過(guò)get()函數(shù)獲取執(zhí)行結(jié)果,該函數(shù)會(huì)阻塞,直到結(jié)果返回。
如果需要使用 Future 但又不提供可用的結(jié)果,則可以聲明 Future<?> 形式類型、并返回 null。
總結(jié):
在Java中所有的線程都是同時(shí)啟動(dòng)的,至于什么時(shí)候,哪個(gè)先執(zhí)行,完全看誰(shuí)先得到CPU的資源。在Java中,每次程序運(yùn)行至少啟動(dòng)兩個(gè)線程。一個(gè)是main線程,一個(gè)是垃圾收集線程。因?yàn)槊慨?dāng)使用Java命令執(zhí)行一個(gè)類的時(shí)候,實(shí)際上都會(huì)啟動(dòng)一個(gè)JVM,每一個(gè)JVM實(shí)際上就是在操作系統(tǒng)中啟動(dòng)了一個(gè)進(jìn)程。