1、線程創(chuàng)建
創(chuàng)建線程有三種方式
繼承Thread類
①定義Thread類的子類,并重寫該類的run方法,該run方法的方法體就代表了線程要完成的任務。因此把run()方法稱為執(zhí)行體。
②創(chuàng)建Thread子類的實例,即創(chuàng)建了線程對象。
③調(diào)用線程對象的start()方法來啟動該線程。實現(xiàn)Runnable接口
①定義runnable接口的實現(xiàn)類,并重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執(zhí)行體。
②創(chuàng)建 Runnable實現(xiàn)類的實例,并依此實例作為Thread的target來創(chuàng)建Thread對象,該Thread對象才是真正的線程對象。
③調(diào)用線程對象的start()方法來啟動該線程。實現(xiàn)Callable接口(jdk1.5新增,在java.util.concurrent包)
①創(chuàng)建Callable接口的實現(xiàn)類,并實現(xiàn)call()方法,該call()方法將作為線程執(zhí)行體,并且有返回值。
②創(chuàng)建Callable實現(xiàn)類的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值。
③使用FutureTask對象作為Thread對象的target創(chuàng)建并啟動新線程。
④調(diào)用FutureTask對象的get()方法來獲得子線程執(zhí)行結束后的返回值
示例代碼
public class ThreadLearning {
//------------------------繼承Thread類-----------------------
//匿名內(nèi)部類的形式
@org.junit.Test
public void generateThread_1() {
Thread thread = new Thread() {
@Override
public void run() {
//do something
System.out.println("1");
}
};
thread.start();
}
//正常形式
@org.junit.Test
public void generateThread_2() {
Thread1 thread1 = new Thread1();
thread1.start();
}
class Thread1 extends Thread{
@Override
public void run() {
//do something
System.out.println("2");
}
}
//------------------------實現(xiàn)Runnable接口-----------------------
//匿名內(nèi)部類的形式
@org.junit.Test
public void generateThread_3() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//do something
System.out.println("1");
}
});
thread.start();
}
//正常形式
@org.junit.Test
public void generateThread_4() {
Thread2 thread2 = new Thread2();
Thread thread = new Thread(thread2);
thread.start();
}
class Thread2 implements Runnable{
@Override
public void run() {
//do something
System.out.println("2");
}
}
//------------------------實現(xiàn)Callable接口-----------------------
@org.junit.Test
public void generateThread_5() {
Thread3 thread3 = new Thread3();
FutureTask<String> futureTask = new FutureTask<String>(thread3);
new Thread(futureTask).start();
try {
String s = futureTask.get();
System.out.println(s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
class Thread3 implements Callable<String>{
@Override
public String call() throws Exception {
return "我是實現(xiàn)Callable接口的線程類";
}
}
}
2、線程狀態(tài)分類
/**
* 一個線程在給定的某個時刻,只能處在下面的6中狀態(tài)中的一種。
*
* 這些狀態(tài)是相對于虛擬機而言的,并不能反映出任何操作系統(tǒng)中線程狀態(tài)。
*
*/
public enum State {
/**
*
* 新建狀態(tài)
*
* 創(chuàng)建后還沒有啟動的線程處在NEW的狀態(tài);
*
* 而啟動線程只有start()方法。也就是說還未調(diào)用start()方法的線程處在NEW的狀態(tài)
*/
NEW,
/**
*
* 運行狀態(tài)
*
* 處在此狀態(tài)的線程有可能正在運行,也有可能正在等待CPU為它分配執(zhí)行時間
* 對于虛擬機而言,處在RUNNABLE狀態(tài)的線程就是正在執(zhí)行。
*
*/
RUNNABLE,
/**
*
* 阻塞狀態(tài)
*
* 處在阻塞狀態(tài)的線程正在等待一個鎖
*
* 在程序等待進入同步區(qū)域的時候,線程將進入這種狀態(tài)。
*
*
*/
BLOCKED,
/**
*
* 無限期等待狀態(tài)
*
* 線程進入無限期等待狀態(tài)的原因是調(diào)用了下面三種方法之一:
* ①沒有設置Timeout參數(shù)的Object.wait()方法
* ②沒有設置Timeout參數(shù)的Thread.join()方法
* ③LockSupport.park()方法
*
*
*/
WAITING,
/**
*
* 限期等待狀態(tài)
*
* 線程進入限期等待狀態(tài)的原因是調(diào)用了下面五種方法之一:
*
* ①設置Timeout參數(shù)的Object.wait()方法
* ②設置Timeout參數(shù)的Thread.join()方法
* ③LockSupport.parkNanos()方法
* ④LockSupport.parkUntil
* ⑤Thread.sleep()方法
*
*/
TIMED_WAITING,
/**
*
* 結束狀態(tài)
*
* 線程已經(jīng)完成執(zhí)行。
*/
TERMINATED;
}
3、狀態(tài)切換圖
4、各個方法的解釋
先看屬于線程Thread的方法
public static native void yield();
該方法向線程調(diào)度器提示,當前線程(調(diào)用該方法的線程對象代表的線程)愿意讓出處理器的使用權,但是線程調(diào)度器可能會忽視該提示。
很少有適合使用該方法的地方。它可能在調(diào)試或者測試的時候,幫助重現(xiàn)由于競爭條件出現(xiàn)的bug。它還可能在設計并發(fā)控制結構的時候提供幫助,比如java.util.concurrent.locks包中的一些類。
public static native void sleep(long millis) throws InterruptedException;
public static void sleep(long millis, int nanos) throws InterruptedException;
該方法會讓當前線程(調(diào)用該方法的線程對象代表的線程)睡眠指定的一段時間(暫時停止執(zhí)行),但當前線程不會失去鎖的擁有權(還會繼續(xù)占用鎖)。
上述兩個方法只是指定的睡眠時間精度不同。
public synchronized void start()
該方法會讓當前線程(調(diào)用該方法的線程對象代表的線程)開始執(zhí)行。JVM虛擬機會調(diào)用該線程的run()方法。對一個線程只能start()一次。
public final synchronized void join(long millis) throws InterruptedException
public final synchronized void join(long millis, int nanos) throws InterruptedException
public final void join() throws InterruptedException
該方法會讓當前線程(調(diào)用該方法的線程,注意,這里并不是thread.join()
中thread所表示的線程,而是方法thread.join()
所在的線程)等待指定時間(如果為0,表示一直等到thread執(zhí)行完),讓join()方法的所屬線程thread執(zhí)行。
join()方法內(nèi)部是調(diào)用wait()方法實現(xiàn)的。
再看Object中的方法
public final native void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException
public final void wait() throws InterruptedException
該方法會讓當前線程(該方法調(diào)用所在的線程)進入等待狀態(tài),直到被該對象(wait()
方法的this
對象)的notify()
或者notifyAll()
喚醒,或者等待時間結束(wait(long timeout)
中的timeout
結束)。
當前線程必須擁有該對象的monitor。
當前線程會將自己放置在等待集中(該對象的等待集),并放棄所有對該對象的同步聲明。該線程就會不可用并保持休眠,直到發(fā)生下面的4種事件中的任意一件:
①其他線程觸發(fā)了該對象的notify方法,那么該線程就可能會被喚醒(并不一定會喚醒,可能很多線程都wait該對象);
②其他線程觸發(fā)了notifyAll方法;
③被其他線程中斷(interrupt()方法)
④設定的等待時間已到。如果是wait(0)
或者wait()
則會一直等待直到喚醒。
上述事件之一發(fā)生后,該線程就會從該對象的等待集中移除,重新加入線程調(diào)度。此后,它就會和其他線程一樣去爭奪該對象的鎖,一旦獲取了該對象的控制權,所有該對象的同步聲明就會重新加載。
正確使用wait()的方式如下:(等待應該總是發(fā)生在輪詢中:waits should always occur in loops
)
synchronized (obj) {
while (condition does not hold)
obj.wait(timeout);
... // Perform action appropriate to condition
}
如果當前線程在等待之前或者等待期間被其他線程打斷(Thread.interrupt()),就會拋出InterruptedException異常。
該方法只應該被擁有對象monitor的線程調(diào)用。
public final native void notify();
public final native void notifyAll();
notify()
喚醒等待該對象monitor的單個線程。如果多個線程都在等待該對象monitor,那么被喚醒的線程是不固定的、任意的。被喚醒的線程并不會立即執(zhí)行,而是等到當前線程放棄了該對象的鎖。被喚醒的線程會和其他線程去競爭對該對象的同步操作。也就是說,被喚醒的線程僅僅是被喚醒去參與競爭,并不是喚醒了就開始執(zhí)行。
該方法只應該被該對象的monitor的所有者線程調(diào)用。一個線程成為該對象的monitor的所有者有以下三種途徑:
①執(zhí)行該對象的同步的實例方法
②執(zhí)行一個同步塊,并且該同步了該對象
③對Class類型的對象,執(zhí)行該類的一個同步的靜態(tài)方法
為什么要使用多線程
①充分利用計算機CPU資源
假如CPU為4核,使用一個線程,那這個線程只會在一個CPU核心工作,其他三個CPU核心就浪費了。
②避免阻塞
在執(zhí)行一件耗時的任務(TASK)的時候(比如下載文件),假如只有一個線程,就只能等任務執(zhí)行完成才能執(zhí)行另外的任務。如果多線程,只需要讓一個線程去執(zhí)行下載任務,另一個線程繼續(xù)處理你其他任務,比如繼續(xù)刷網(wǎng)頁。
③并行處理任務
比如有很多任務需要執(zhí)行(比如10個人同時要下載文件),如果只有一個線程,只能逐一去下載,排在第一個的人很高興,但是后面的人需要等到前面的人下載完才能輪到自己,會很不樂意。使用多線程(假如有10個線程),每個線程對應一個下載任務,CPU就會同時(只是看上去同時)執(zhí)行這些線程,這樣每個人都可以以基本相同的速度下載完文件。
一個實際的應用就是B/S架構的應用,并行處理大量請求。
④就想到這么多。。。
內(nèi)容參考
Java多線程學習(二)---線程創(chuàng)建方式
jdk1.8
《深入理解Java虛擬機》
《Java并發(fā)編程的藝術》