前言
并發是一件很美妙的事情,線程的調度與使用會讓你除了業務代碼外,有新的世界觀,無論你是否參與但是這對于你未來的成長幫助很大。
所以,讓我們來好好看看在JAVA中啟動線程的那幾個方式與介紹。
Thread
對于 Thread 我想這個基本上大家都認識的,在Java源碼是這樣說: java 虛擬機允許應用程序同時運行多個執行線程。 而這個的 Thread 就是程序的執行線程。
如何使用它呢,其實在這個類中的源碼已經給我們寫好了,甚至是下面的 Runnable 的使用方式。(如下是Thread源碼)
/** * A <i>thread</i> is a thread of execution in a program. The Java * Virtual machine allows an Application to have multiple threads of * execution running concurrently. * <hr><blockquote><pre> * class PrimeThread extends Thread { * long minPrime; * PrimeThread(long minPrime) { * this.minPrime = minPrime; * } * * public void run() { * // compute primes larger than minPrime * . . . * } * } * </pre></blockquote><hr> * <p> * The following code would then create a thread and start it running: * <blockquote><pre> * PrimeThread p = new PrimeThread(143); * p.start(); * </pre></blockquote> * <p> * <hr><blockquote><pre> * class PrimeRun implements Runnable { * long minPrime; * PrimeRun(long minPrime) { * this.minPrime = minPrime; * } * * public void run() { * // compute primes larger than minPrime * . . . * } * } * </pre></blockquote><hr> * <p> * The following code would then create a thread and start it running: * <blockquote><pre> * PrimeRun p = new PrimeRun(143); * new Thread(p).start(); * </pre></blockquote> * <p> */public class Thread implements Runnable { //...}
閱讀源碼的信息其實是最全的 ,我截取了部分的注釋信息,起碼我們現在可以無壓力的使用這個兩個方式來啟動自己的線程。
如果我們還要傳遞參數的話,那么我們設定一個自己的構造函數也是可以,如下方式:
public class MyThread extends Thread { public MyThread(String name) { super(name); } @Override public void run() { System.out.println("一個子線程 BY " + getName()); }}
這時讀者應該發現,這個構造函數中的 name ,居然在 Thread 中也是有的,其實在Java中的線程都會自己的名稱,如果我們不給其定義名稱的話,java也會自己給其命名。
/** * Allocates a new {@code Thread} object. This constructor has the same * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread} * {@code (null, null, name)}. * * @param name * the name of the new thread */public Thread(String name) { init(null, null, name, 0);}
而我們最核心,也是大家最在意的應該就是如何啟動并執行我們的線程了,是的,這個大家都知道的,就是這個 run 方法了。
同時大家如果了解過了 Runnable ,我想大家都會知道這個 run 方法,其實是 Runnable 的方法,而我們本節的 Thread 也是實現了這個接口。
這里,大家可能會好奇,不是應該是 start 這個方法嗎?那么讓我們看看 start 的源碼。
/** * Causes this thread to begin execution; the Java Virtual Machin * calls the <code>run</code> method of this thread. */public synchronized void start() { //...}
通過 start 方法,我們可以了解到,就如同源碼的啟動模板中那樣,官網希望,對于線程的啟動,使用者是通過 start 的方式來啟動線程,因為這個方法會讓Java虛擬機會調用這個線程的 run 方法。
其結果就是,一個線程去運行 start 方法,而另一個線程則取運行 run 方法。同時對于這樣線程,Java官方也說了,線程是不允許多次啟動的,這是不合法的。
所以如果我們執行下面的代碼,就會報 java.lang.IllegalThreadStateException 異常。
MyThread myThread = new MyThread("Thread");myThread.start();myThread.start();
但是,如果是這樣的代碼呢?
MyThread myThread = new MyThread("Thread");myThread.run();myThread.run();myThread.start();//運行結果一個子線程 BY Thread一個子線程 BY Thread一個子線程 BY Thread
這是不合理的,如果大家有興趣,可以去試試并動手測試下,最好開調試模式。
下面我們再看看,連 Thread 都要實現,且核心的 run 方法出處的 Runnable 。
Runnable
比起 Thread 我希望大家跟多的使用 Runnable 這個接口實現的方式,對于好壞對比會在總結篇說下。
我想大家看 Runnable 的源碼會更加容易與容易接受,畢竟它有一個 run 方法。(如下為其源碼)
/** * The <code>Runnable</code> interface should be implemented by any * class whose instances are intended to be executed by a thread. The * class must define a method of no arguments called <code>run</code>. */@FunctionalInterfacepublic interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. */ public abstract void run();}
首先,所有打算執行線程的類均可實現這個 Runnable 接口,且必須實現 run 方法。
它將為各個類提供一個協議,就像 Thread 一樣,其實當我們的類實現了 Runnable 的接口后,我們的類與 Thread 是同級,只是可能僅有 run 方法,而沒有 Thread 提供的跟豐富的功能方法。
而對于 run 方法,則是所有實現了 Runnable 接口的類,在調用 start 后,將使其單獨執行 run 方法。
那么我們可以寫出這樣的測試代碼。
MyThreadRunnable myThreadRunnable = new MyThreadRunnable("Runnabel");myThreadRunnable.run();new Thread(myThreadRunnable).start();Thread thread = new Thread(myThreadRunnable);thread.start();thread.start();//運行效果Exception in thread "main" java.lang.IllegalThreadStateException at java.lang.Thread.start(Thread.java:705) at com.github.myself.runner.RunnableApplication.main(RunnableApplication.java:14)這是一個子線程 BY Runnabel這是一個子線程 BY Runnabel這是一個子線程 BY Runnabel
同樣的,線程是不允許多次啟動的,這是不合法的。
同時,這時我們也看出了使用 Thread 與 Runnable 的區別,當我們要多次啟用一個相同的功能時。
我想 Runnable 更適合你。
但是,用了這兩個方式,我們要如何知道線程的運行結果呢???
FutureTask
這個可能很少人(初學者)用到,不過這個現在是我最感興趣的。它很有趣。
其實還有一個小兄弟,那就是 Callable。 它們是一對搭檔。如果上面的內容,你已經細細品味過,那么你應該已經發現 Callable 了。
沒錯,他就在 Runnable 的源碼中出現過。
/** * @author Arthur van Hoff * @see java.lang.Thread * @see java.util.concurrent.Callable * @since JDK1.0 */ @FunctionalInterfacepublic interface Runnable {}
那么我們先去看看這個 Callable 吧。(如下為其源碼)
/** * A task that returns a result and may throw an exception. * Implementors define a single method with no arguments called * {@code call}. */@FunctionalInterfacepublic 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;}
其實,這是一個與 Runnable 基本相同的接口,當時它可以返回執行結果與檢查異常,其計算結果將由 call() 方法返回。
那么其實我們現在可以寫出一個實現的類。
public class MyCallable implements Callable { private String name; public MyCallable(String name) { this.name = name; } @Override public Object call() throws Exception { System.out.println("這是一個子線程 BY " + name); return "successs"; }}
關于更深入的探討,我將留到下一篇文章中。
好了,我想我們應該來看看 FutureTask 這個類的相關信息了。
/** * A cancellable asynchronous computation. This class provides a base * implementation of {@link Future}, with methods to start and cancel * a computation, query to see if the computation is complete, and * retrieve the result of the computation. The result can only be * retrieved when the computation has completed; the {@code get} * methods will block if the computation has not yet completed. Once * the computation has completed, the computation cannot be restarted * or cancelled (unless the computation is invoked using * {@link #runAndReset}). */ public class FutureTask<V> implements RunnableFuture<V> { //... }
源碼寫的很清楚,這是一個可以取消的異步計算,提供了查詢、計算、查看結果等的方法,同時我們還可以使用 runAndRest 來讓我們可以重新啟動計算。
在查看其構造函數的時候,很高興,我們看到了我們的 Callable 接口。
/** * Creates a {@code FutureTask} that will, upon running, execute the * given {@code Callable}. * * @param callable the callable task * @throws NullPointerException if the callable is null */public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW; // ensure visibility of callable}
即我們將創建一個未來任務,來執行 Callable 的實現類。那么我們現在可以寫出這樣的代碼了。
final FutureTask fun = new FutureTask(new MyCallable("Future"));
那么接下來我們就可以運行我們的任務了嗎?
是的,我知道了 run() 方法,但是卻沒有 start 方法。
官方既然說有結果,那么我找到了 get 方法。同時我嘗試著寫了一下測試代碼。
public static void main(String[] args) { MyCallable myCallable = new MyCallable("Callable"); final FutureTask fun = new FutureTask(myCallable); fun.run(); try { Object result = fun.get(); System.out.println(result); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); }}
運行效果,是正常的,這好像是那么回事。
//運行效果這是一個子線程 BY Callablesuccesss
可是,在我嘗試著加多一些代碼的時候,卻發現了一些奇妙的東西 。
我加多了一行 fun.run(); 代碼,同時在 MyCallable 類中,將方法加一個時間線程去等待3s。
結果是: 結果只輸出了一次,同時 get 方法需要等運行3s后才有返回。
這并不是我希望看到的。但是,起碼我們可以知道,這次即使我們多次運行使用 run 方法,但是這個線程也只運行了一次。這是一個好消息。
同時,我們也拿到了任務的結果,當時我們的進程被阻塞了,我們需要去等我們的任務執行完成。
最后,在一番小研究后,以下的代碼終于完成了我們預期的期望。
public static void main(String[] args) { MyCallable myCallable = new MyCallable("Callable"); ExecutorService executorService = Executors.newCachedThreadPool(); final FutureTask fun = new FutureTask(myCallable); executorService.execute(fun);// fun.run(); //阻塞進程 System.out.println("--繼續執行"); try { Object result = fun.get(); System.out.println(result); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); }}
我們使用線程池去運行我們的 FutureTask 同時使用 get 方法去獲取運行后的結果。結果是友好的,進程并不會被阻塞。
關于更深入的探討,我將留到下一篇文章中。
總結一波
好了,現在應該來整理以下了。
- Thread 需要我們繼承實現,這是比較局限的,因為Java的 繼承資源 是有限的,同時如果多次執行任務,還需要 多次創建任務類。
- Runnable 以接口的形式讓我們實現,較為方便,同時多次執行任務也無需創建多個任務類,當時僅有一個 run 方法。
- 以上兩個方法都 無法獲取任務執行結果 ,FutureTask可以獲取任務結果。同時還有更多的新特性方便我們使用···