本文首發於 貓叔的博客,轉載請申明出處
併發是一件很美妙的事情,線程的調度與使用會讓你除了業務代碼外,有新的世界觀,不管你是否參與可是這對於你將來的成長幫助很大。java
因此,讓咱們來好好看看在Java中啓動線程的那幾個方式與介紹。git
對於 Thread 我想這個基本上你們都認識的,在Java源碼是這樣說: java 虛擬機容許應用程序同時運行多個執行線程。
而這個的 Thread 就是程序的執行線程。github
如何使用它呢,其實在這個類中的源碼已經給咱們寫好了,甚至是下面的 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 { //... }
閱讀源碼的信息實際上是最全的 ,我截取了部分的註釋信息,起碼咱們如今能夠無壓力的使用這個兩個方式來啓動本身的線程。併發
若是咱們還要傳遞參數的話,那麼咱們設定一個本身的構造函數也是能夠,以下方式:app
public class MyThread extends Thread { public MyThread(String name) { super(name); } @Override public void run() { System.out.println("一個子線程 BY " + getName()); } }
這時讀者應該發現,這個構造函數中的 name ,竟然在 Thread 中也是有的,其實在Java中的線程都會本身的名稱,若是咱們不給其定義名稱的話,java也會本身給其命名。less
/** * 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 也是實現了這個接口。async
這裏,你們可能會好奇,不是應該是 start 這個方法嗎?那麼讓咱們看看 start 的源碼。ide
/** * 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
。
比起 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>. */ @FunctionalInterface public 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
更適合你。
可是,用了這兩個方式,咱們要如何知道線程的運行結果呢???
這個可能不多人(初學者)用到,不過這個如今是我最感興趣的。它頗有趣。
其實還有一個小兄弟,那就是 Callable
。 它們是一對搭檔。若是上面的內容,你已經細細品味過,那麼你應該已經發現 Callable
了。
沒錯,他就在 Runnable
的源碼中出現過。
/** * @author Arthur van Hoff * @see java.lang.Thread * @see java.util.concurrent.Callable * @since JDK1.0 */ @FunctionalInterface public 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}. */ @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; }
其實,這是一個與 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 Callable successs
但是,在我嘗試着加多一些代碼的時候,卻發現了一些奇妙的東西 。
我加多了一行 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
方法去獲取運行後的結果。結果是友好的,進程並不會被阻塞。
關於更深刻的探討,我將留到下一篇文章中。
好了,如今應該來整理如下了。
run
方法。現架構設計(碼農)兼創業技術顧問,不羈平庸,熱愛開源,雜談程序人生與不按期乾貨。