建立線程的四種方式
- 繼承Thread類
- 實作Runnable接口
- 實作Callable接口
- Callable和Runnable的異同點
- 使用線程池Executors
繼承Thread類
(1)建立一個類繼承Thread類,重寫run()方法,将所要完成的任務代碼寫進run()方法中;
(2)建立Thread類的子類的對象;
(3)調用該對象的start()方法
start()方法表示啟用該線程,之後會自動調用run()方法,程式列印的就是Thread;如果直接調用run方法,那程式列印的就是main
執行個體代碼
public class Main2 {
public static void main(String[] args) throws Exception {
new Thread1().start();
//new Thread1().run();
}
}
class Thread1 extends Thread {
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
}
實作Runnable接口
(1)建立一個類并實作Runnable接口
(2)重寫run()方法,将所要完成的任務代碼寫進run()方法中
(3)建立實作Runnable接口的類的對象,将該對象當做Thread類的構造方法中的參數傳進去
(4)使用Thread類的構造方法建立一個對象,并調用start()方法即可運作該線程
public class Main2 {
public static void main(String[] args) throws Exception {
new Thread(new Thread2()).start();
}
}
class Thread2 implements Runnable{
@Override
public void run() {
System.out.println("Runnable...."+Thread.currentThread().getName());
}
}
實作Callable接口
(1)建立一個類并實作Callable接口
(2)重寫call()方法,将所要完成的任務的代碼寫進call()方法中,需要注意的是call()方法有傳回值,并且可以抛出異常
(3)如果想要擷取運作該線程後的傳回值,需要建立Future接口的實作類的對象,即FutureTask類的對象,調用該對象的get()方法可擷取call()方法的傳回值
(4)使用Thread類的有參構造器建立對象,将FutureTask類的對象當做參數傳進去,然後調用start()方法開啟并運作該線程。
public class Main2 {
public static void main(String[] args) throws Exception {
FutureTask<String> task = new FutureTask<String>(new Thread4());
Thread thread = new Thread(task);
thread.start();
String s = task.get();
System.out.println(s);
}
}
class Thread4 implements Callable{
@Override
public Object call() throws Exception {
System.out.println(Thread.currentThread().getName()+"-----列印的");
return Thread.currentThread().getName()+"----傳回的";
}
}
Callable和Runnable的異同點
相同點
都是接口
都可以編寫多線程程式
都采用Thread.start()啟動線程
不同點
1 Callable規定的方法是call(),而Runnable規定的方法是run().
2 Callable的任務執行後可傳回值,而Runnable的任務是不能傳回值的。
3 call()方法可抛出異常,而run()方法是不能抛出異常的。–run()方法異常隻能在内部消化,不能往上繼續抛
4 運作Callable任務可拿到一個Future對象, Future表示異步計算的結果。
5 它提供了檢查計算是否完成的方法,以等待計算的完成,并檢索計算的結果。
6 通過Future對象可了解任務執行情況,可取消任務的執行,還可擷取任務執行的結果。
7 Callable是類似于Runnable的接口,實作Callable接口的類和實作Runnable的類都是可被其它線程執行的任務。
注:Callalbe接口支援傳回執行結果,需要調用FutureTask.get()得到,此方法會阻塞主程序的繼續往下執行,如果不調用不會阻塞。
使用線程池Executors
(1)使用Executors類中的newFixedThreadPool(int num)方法建立一個線程數量為num的線程池
(2)調用線程池中的execute()方法執行由實作Runnable接口建立的線程;調用submit()方法執行由實作Callable接口建立的線程
(3)調用線程池中的shutdown()方法關閉線程池
public class ThreadPoolTest {
public static void main(String[] args) {
//test1();
//test2();
//test3();
//test4();
//test5();
}
public static void test1(){
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i=0 ; i < 100 ; i++){
int temp = i;
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("threadName;"+Thread.currentThread().getName()+",i"+temp);
}
});
}
}
public static void test2(){
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
for (int i=0 ; i < 3 ; i++){
int temp = i;
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"------------------------"+temp);
}
}, 1,3, TimeUnit.SECONDS);
}
}
public static void test3(){
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i=0 ; i < 10 ; i++){
int temp = i;
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("delay 3 seconds");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
public static void test4(){
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i=0 ; i < 10 ; i++){
int temp = i;
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"------------------------"+temp);
}
});
}
}
public void test5(){
ExecutorService executorService = Executors.newWorkStealingPool();
for (int i=0 ; i < 10 ; i++){
int temp = i;
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"------------------------"+temp);
}
});
}
}
}
-
newSingleThreadPool,為單核心線程池,最大線程也隻有一個它的線程數存活時間是無限的
建立一個執行器,該執行器使用一個工作線程操作一個無界隊列。(但是請注意,如果這個線程在關閉之前的執行過程中由于失敗而終止,那麼如果需要執行後續任務,将會有一個新的線程替代它。)任務保證按順序執行,并且在任何給定時間都不會有多個任務處于活動狀态。與其他等價的newFixedThreadPool(1)不同,傳回的執行器保證不可重新配置以使用其他線程。
- newFixedThreadPool,我們需要傳入一個固定的核心線程數,并且核心線程數等于最大線程數,而且它們的線程數存活時間都是無限的
- newCachedThreadPool,可以進行緩存的線程池,意味着它的線程數是最大的,無限的。但是核心線程數為 0,這沒關系。這裡要考慮線程的摧毀,因為不能夠無限的建立新的線程,是以在一定時間内要摧毀空閑的線程。但是由于這種線程池建立時初始化的都是無界的值,一個是最大線程數,一個是任務的阻塞隊列,都沒有設定它的界限,這可能會出現問題。
- newScheduledThreadPool,這個表示的是有計劃性的線程池,就是在給定的延遲之後運作,或周期性地執行。很好了解,大家應該用過 Timer 定時器類吧,這兩個差不多的意思
- newWorkStealingPool,這個是 JDK1.8 版本加入的一種線程池,stealing 翻譯為搶斷、竊取的意思,它實作的一個線程池和上面4種都不一樣,用的是 ForkJoinPool 類。最明顯的用意就是它是一個并行的線程池,參數中傳入的是一個線程并發的數量,這裡和之前就有很明顯的差別,前面4種線程池都有核心線程數、最大線程數等等,而這就使用了一個并發線程數解決問題。從介紹中,還說明這個線程池不會保證任務的順序執行,也就是 WorkStealing 的意思,搶占式的工作。
注對比了以上 5 種線程池,我們看到每個線程池都有自己的特點,這也是為我們封裝好的一些比較常用的線程池。當然,我建議你在使用(3)可緩存的線程池時,盡量的不要用預設的那個來建立,因為預設值都是無界的,可能會出現一些問題,這時我們可以參考源碼中的線程池初始化參數的設定,可以盡可能的避免錯誤發生。
注newSingleThreadPool()與newFixedThreadPool(1)的不同,前者線程池中的線程數量是不可重新配置的,也即不能去加入額外的線程。
corePoolSize : 表示線程池核心線程數,當初始化線程池時,會建立核心線程進入等待狀态,即使它是空閑的,核心線程也不會被摧毀,進而降低了任務一來時要建立新線程的時間和性能開銷。
maximumPoolSize : 表示最大線程數,意味着核心線程數都被用完了,那隻能重新建立新的線程來執行任務,但是前提是不能超過最大線程數量,否則該任務隻能進入阻塞隊列進行排隊等候,直到有線程空閑了,才能繼續執行任務。
keepAliveTime : 表示線程存活時間,除了核心線程外,那些被新建立出來的線程可以存活多久。意味着,這些新的線程一但完成任務,而後面都是空閑狀态時,就會在一定時間後被摧毀。
unit : 存活時間機關,沒什麼好解釋的,一看就懂。
workQueue : 表示任務的阻塞隊列,由于任務可能會有很多,而線程就那麼幾個,是以那麼還未被執行的任務就進入隊列中排隊,隊列我們知道是 FIFO 的,等到線程空閑了,就以這種方式取出任務。這個一般不需要我們去實作。