Java併發編程之實現多線程的方法

實現線程的正確方法:2種

  • 實現Runnable接口創建線程
/**
 * 實現Runnable接口創建線程
 */
public class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println("實現Runnable接口創建線程");
    }

    public static void main(String[] args) {
        new Thread(new MyThread()).start();
    }
}
  • 繼承Thread類創建線程
/**
 * 繼承Thread類創建線程
 */
public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("繼承Thread類創建線程");
    }

    public static void main(String[] args) {
        new Thread(new MyThread()).start();
    }
}

實現Runnable接口創建線程的方法更好

  1. 解耦:具體執行的任務,也就是run方法應該與線程的創建、運行、銷燬是不相關的;
  2. 資源節約:如果用繼承Thread這種方法,每次新建個任務只能新建個線程,線程的創建、運行和銷燬的消耗是比較大的;如果使用實現Runnable接口的方法,後續我們可以使用線程池等工具,不用再去新建線程,帶來的損耗會大大降低;
  3. 擴展性:Java是不支持多繼承的,如果採用繼承Thread類的方法,那麼子類就無法再去繼承其他類,大大的限制了子類的擴展性

兩種方法的本質對比

讓我們看看Thread類裏面的run()是怎麼寫的:

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
  • 代碼非常的簡單,意思是有Runnable類型的target對象,那麼就執行target對象裏面的run();
  • 使用實現Runnable接口方法時,因爲傳入了Runnable對象,所以新建線程對象裏面執行了target.run();
  • 使用繼承Thread類方法時,因爲子類重寫了Thread類的run(),所以最終執行子類的run();

同時使用兩種方法會怎麼樣?

public class MyThread {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("實現Runnable接口創建線程");
            }
        }) {
            @Override
            public void run() {
                System.out.println("繼承Thread類創建線程");
            }
        }.start();
    }
}
繼承Thread類創建線程

上述代碼雖然傳入了Runnable對象,但是子類重寫了Thread類的run(),導致該方法不再調用target.run(),所以同時使用兩種方法,只有繼承Thread類方法才生效!

最精準的描述

  • 通常可以分爲兩類:實現Runnable接口、繼承Thread類重寫run()
  • 準確的講,創建線程只有一種方式:創建Thread對象,而在Thread類裏面,實現run()有兩種不同的情況:
    1. 實現Runnable接口,並把實例對象傳給Thread類去執行run();
    2. 繼承Thread類,直接重寫了Thread類的run();

典型的錯誤觀點

  • “線程池創建線程”
public class MyThread {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 100; i++) {
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName());
                }
            });
        }
    }
}
pool-1-thread-14
pool-1-thread-9
pool-1-thread-8
pool-1-thread-13
pool-1-thread-15
pool-1-thread-16
......

結果表明,線程池確實創建了新的線程,但是這只是表現,其內部是通過實現ThreadFactory接口來創建線程的,如下所示:

public interface ThreadFactory {

    /**
     * Constructs a new {@code Thread}.  Implementations may also initialize
     * priority, name, daemon status, {@code ThreadGroup}, etc.
     *
     * @param r a runnable to be executed by new thread instance
     * @return constructed thread, or {@code null} if the request to
     *         create a thread is rejected
     */
    Thread newThread(Runnable r);
}
  • “通過Callable和FutureTask創建線程,也算一種新的創建線程的方式”
public class CallableAndFutureTask {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask futureTask = new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {
                return Thread.currentThread().getName();
            }
        });
        new Thread(futureTask).start();
        System.out.println(futureTask.get());
    }
}
Thread-0

實現Callable接口並創建FutureTask對象的方法確實也可以創建線程,但是和線程池一樣,這只是對創建Thread對象的一種包裝,如下圖所示:

public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

FutureTask實現了RunnableFuture接口,而RunnableFuture繼承與Runnable接口,根據這種傳遞關係,相當於FutureTask實現了Runnable接口,在實現的run()方法中調用了Callable接口的call方法來獲取返回值,並把返回值set到result中,最後通過get()獲取到返回值,如下圖所示:

    public void run() {
        .......
          Callable<V> c = callable;
	      result = c.call();
          ran = true;
          if (ran) set(result);
          }
        ......
    }

以上兩種說法,只是對於實現Runnable接口和繼承Thread類方法的封裝,本質上不算新的方法!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章