天天看點

你真得知道Java 中有幾種建立線程的方式嗎?一、背景二、分析三、總結

一、背景

本文給出兩個簡單卻很有意思的線程相關的題目

題目1:

Java 中有幾種建立線程的方式?

如果面試中遇到這個問題,估計很多人會非常開心,然而網上的諸多答案真的對嗎?

題目2:

public static void main(String[] args) {
    Thread thread = new Thread(() -> System.out.println("Runnable run")) {
        @Override
        public void run() {
            System.out.println("Thread run");
        }
    };
    thread.start();
}           

請問運作後輸出的結果是啥?

拿到這個問題有些同學可能會懵掉幾秒鐘,什麼鬼...

二、分析

2.1 有幾種建立形成的方式

不知道大家想過沒有,本質上 JDK 8 中提供了幾種建立線程的方式?

可能很多人會講可以先建立 Runnable 當做參數傳給 Thread ,可以寫匿名内部類,可以編寫 Thread 的子類,可以通過線程池等等。

其實線程池的 Worker 内部還是通過 Thread 執行的,而Worker 中的線程是通過 ThreadFactory 建立,ThreadFactory 最終還是通過構造 Thread 或者 Thread 子類的方式建立線程的。

org.apache.tomcat.util.threads.TaskThreadFactory

為例:

/**
 * Simple task thread factory to use to create threads for an executor
 * implementation.
 */
public class TaskThreadFactory implements ThreadFactory {

    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;
    private final boolean daemon;
    private final int threadPriority;

    public TaskThreadFactory(String namePrefix, boolean daemon, int priority) {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
        this.namePrefix = namePrefix;
        this.daemon = daemon;
        this.threadPriority = priority;
    }

    @Override
    public Thread newThread(Runnable r) {
        TaskThread t = new TaskThread(group, r, namePrefix + threadNumber.getAndIncrement());
        t.setDaemon(daemon);
        t.setPriority(threadPriority);

        // Set the context class loader of newly created threads to be the class
        // loader that loaded this factory. This avoids retaining references to
        // web application class loaders and similar.
        if (Constants.IS_SECURITY_ENABLED) {
            PrivilegedAction<Void> pa = new PrivilegedSetTccl(
                    t, getClass().getClassLoader());
            AccessController.doPrivileged(pa);
        } else {
            t.setContextClassLoader(getClass().getClassLoader());
        }

        return t;
    }
}           

TaskThread

源碼:

public class TaskThread extends Thread {
//...
}           

其實從本質上講隻有一種建立對象的方式,就是通過建立 Thread 或者子類的方式。

接下來讓我們看下 Thread 類的注釋:

/**
* There are two ways to create a new thread of execution. 
* One is to
* declare a class to be a subclass of <code>Thread</code>. This
* subclass should override the <code>run</code> method of class
* <code>Thread</code>. An instance of the subclass can then be
* allocated and started. For example, a thread that computes primes
* larger than a stated value could be written as follows:
* <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>
* The other way to create a thread is to declare a class that
* implements the <code>Runnable</code> interface. That class then
* implements the <code>run</code> method. An instance of the class can
* then be allocated, passed as an argument when creating
* <code>Thread</code>, and started. The same example in this other
* style looks like the following:
* <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 {

// 省略其他
}           

通過注釋我們可以看出有兩種建立執行線程的方式:

  • 繼承 Thread 并且重寫 run 方法。
  • 實作 Runnable 接口實作 run 方法,并作為參數來建立 Thread。

如果是從這個層面上講,有兩種建立 Thread 的方式,其他方式都是這兩種方式的變種。

2.2 運作結果是啥?

2.2.1 回顧

可能很多同學看到這裡會有些懵。

如果下面這種寫法(寫法1):

public static void main(String[] args) {
    Thread thread = new Thread(() -> System.out.println("Runnable run"));
    thread.start();
}           

答案是列印: "Runnable run"

如果這麼寫(寫法2):

public static void main(String[] args) {
    Thread thread = new Thread() {
        @Override
        public void run() {
            System.out.println("Thread run");
        }
    };
    thread.start();
}           

答案是列印“Thread run”

一起寫,這個操作有點風騷...

2.2.2 莫慌

我們可以确定的是 thread.start 調用的是 run 方法,既然這裡重寫了 run 方法,肯定調用的是咱們重寫的 run 方法。

是以答案是 "Thread run“。

為了更好地搞清楚這個問題,咱麼看下源碼:

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}           

target 部分:

/* What will be run. */
private Runnable target;

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {

    // ....
    this.target = target;

}           

我們再看下預設的 run 方法:

/**
 * If this thread was constructed using a separate
 * <code>Runnable</code> run object, then that
 * <code>Runnable</code> object's <code>run</code> method is called;
 * otherwise, this method does nothing and returns.
 * <p>
 * Subclasses of <code>Thread</code> should override this method.
 *
 * @see     #start()
 * @see     #stop()
 * @see     #Thread(ThreadGroup, Runnable, String)
 */
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}           

注釋說的很清楚,通過構造方法傳入 Runnable ,則調用 Runnable的 run 方法,否則啥都不幹。

是以這就是為什麼寫法1 的結果是:"Runnable run"。

如果咱們重寫了 run 方法,預設 target 的就失效了,是以結果就是"Thread run“。

如果我想先執行 Runnbale 的 run 方法再執行咱們的列印"Thread run“咋辦?

既然上面了解到父類的預設行為是執行構造函數中 Runnbale 對象的 run 方法,咱麼先調用 super.run 不就可以了嗎:

public static void main(String[] args) {
    Thread thread = new Thread(() -> System.out.println("Runnable run")) {
        @Override
        public void run() {
            super.run();
            System.out.println("Thread run");
        }
    };
    thread.start();
}           

輸出結果:

Runnable run

Thread run

其實這個題目重點考察大家有沒有認真看過源碼,有沒有真正了解多态的含義,是否都線程基礎有一定的了解。

三、總結

這個問題本質上很簡單,實際上很多人第一反應會懵掉,是因為很少主動去看 Thread 源碼。

學習和工作的時候更多地是學會用,而不是多看源碼,了解原理。

通過這個簡單的問題,希望大家學習和工作之餘可以養成檢視源碼的習慣,多動手練習,多思考幾個為什麼。

希望大家讀書時,尤其是看部落格文章時,不要想當然,多思考下問題的本質。

如果你覺得本文對你有幫助,歡迎點贊評論,你的支援和鼓勵是我創作的最大動力。
你真得知道Java 中有幾種建立線程的方式嗎?一、背景二、分析三、總結