簡說Java線程的那幾個啓動方式

本文首發於 貓叔的博客,轉載請申明出處

前言

併發是一件很美妙的事情,線程的調度與使用會讓你除了業務代碼外,有新的世界觀,不管你是否參與可是這對於你將來的成長幫助很大。java

因此,讓咱們來好好看看在Java中啓動線程的那幾個方式與介紹。git

Thread

對於 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
 *             &nbsp;.&nbsp;.&nbsp;.
 *         }
 *     }
 * </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
 *             &nbsp;.&nbsp;.&nbsp;.
 *         }
 *     }
 * </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

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

一樣的,線程是不容許屢次啓動的,這是不合法的。

同時,這時咱們也看出了使用 ThreadRunnable 的區別,當咱們要屢次啓用一個相同的功能時。

我想 Runnable 更適合你。

可是,用了這兩個方式,咱們要如何知道線程的運行結果呢???

FutureTask

這個可能不多人(初學者)用到,不過這個如今是我最感興趣的。它頗有趣。

其實還有一個小兄弟,那就是 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 方法去獲取運行後的結果。結果是友好的,進程並不會被阻塞。

關於更深刻的探討,我將留到下一篇文章中。

總結一波

好了,如今應該來整理如下了。

  • Thread 須要咱們繼承實現,這是比較侷限的,由於Java的 繼承資源 是有限的,同時若是屢次執行任務,還須要 屢次建立任務類
  • Runnable 以接口的形式讓咱們實現,較爲方便,同時屢次執行任務也無需建立多個任務類,當時僅有一個 run 方法。
  • 以上兩個方法都 沒法獲取任務執行結果 ,FutureTask能夠獲取任務結果。同時還有更多的新特性方便咱們使用···

公衆號:Java貓說

現架構設計(碼農)兼創業技術顧問,不羈平庸,熱愛開源,雜談程序人生與不按期乾貨。

Image Text

相關文章
相關標籤/搜索