只有一種實現多線程的方式 ?

線程是實現多線程的基礎,本篇主要講解關於線程的兩個問題。
1 爲什麼說本質上只有一種實現多線程的方式?
2 實現Runnable的方式和繼承Thread的方式哪種好?好在哪裏?

爲什麼說本質上只有一種實現多線程的方式?

  • 在回答這個問題之前,我們首先看看常用的實現多線程的方式。

1 實現Runnable接口的方式

class RunnableImpl : Runnable {
    override fun run() {
        println("this is test for runnable")
    }
}

fun main() {
    Thread(RunnableImpl()).start()
}
  • 通過實現Runnable接口的方式,並且實現其中的run方法,並將實現run方法的實例對象作爲Thread的構造方法參數,然後調用新創建的Thread的start方法,就可以實現多線程了。

2 繼承Thread類的方式

class ThreadExtend : Thread() {
    override fun run() {
        println("this is test for thread")
    }
}

fun main() {
    ThreadExtend().start()
}
  • 通過繼承Thread的方式,並且重寫其中的run方法,然後用此實例對象調用start方法即可實現多線程。

3 通過Executors線程池模型實現多線程

fun main() {
    Executors.newSingleThreadExecutor().execute {
        println("thread pool")
    }
}
  • 通過Executors靜態工廠方法模式,可以創建多種ExecutorServiceThreadPoolExecutor實例對象。其中ThreadPoolExecutor中有一個參數爲ThreadFactory,它的默認實現是DefaultThreadFactory,下面我們看一下它的實現:
public interface ThreadFactory {
    Thread newThread(Runnable r);
}

private static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
}
  • 線程池模型中的線程是怎麼來的呢?主要就是看newThread方法的實現了,我們可以看到首先通過構造方法傳入了方法名字,然後設置是否爲守護線程,設置線程的優先級,最終將此線程返回了。所以通過線程池創建線程的方式還是屬於通過前面new Thread()的方式實現的。

4 通過Callable實現多線程

class CallableImpl : Callable<String> {
   override fun call(): String {
       Thread.sleep(3000)
       return "callable impl"
   }
}

fun main() {
   println("before future get")
   Executors.newSingleThreadExecutor().submit(CallableImpl()).let {
       println(it.get())
   }
   println("after future get")
}
  • 通過Callable的方式和Runnable的方式的區別是前者是有返回值的,並且調用future.get()方法會阻塞當前線程直到獲取到返回值。但是實際上調用了submit(callable)之後,內部還是將Callable封裝爲了Runnable,這種實現多線程的方式還是離不開最開始說的兩種方式。

實現多線程只有一種方式

  • 其實通過上面的例子就會發現,不管實現多線程的方式千變萬化,肯定離不開上面最開始的兩種方式,下面我們對最開始的兩種方式做一個分析,來回答爲什麼說本質上只有一種實現多線程的方式?
  • 調用Thread的start方法,會執行run方法,我們查看Thread中run方法的源碼:
    class Thread {
    	public Thread(Runnable target) {
            init(null, target, "Thread-" + nextThreadNum(), 0);
        }
        
        private Runnable target;
    	  @Override
          public void run() {
              if (target != null) {
                  target.run();
              }
          }
    }
    
  • 如果通過實現Runnable的方式的話,執行run方法的時候,內部的target不爲null,執行的就是我們自定義Runnable中實現的run方法的內容。
  • 如果通過繼承Thread的方式的話,就會重寫這裏的run方法,就執行的是自定義的Thread的run方法。
  • 所以實際上實現多線程唯一的方式就是通過構造一個Thread類,這也是本質上實現多線程的唯一方式。
  • 其實上面不管是上面的Runnable也好,Callable也好,重寫Thread的run方法也好,他們並沒有實現多線程的功能,他們只是提供了多線程需要執行的內容,按照此思路,實現多線程的方式就會越來越多,但是本質上還是上面說的唯一的一種方式。

Runnable和Thread哪種方式好?好在哪裏?

下面我們對這兩種方式做一下對比,就知道Runnable方式爲什麼比Thread方式好。

  • 1 從代碼架構的角度來,實現Runnable的方式,內部只定義了需要執行的內容,這樣就將線程的執行和執行的內容分開了,達到了解耦的目的。
  • 2 從性能方方面考慮,因爲線程的創建和銷燬是有開銷的,如果創建和銷燬線程所引起的性能消耗遠遠大於執行run方法所帶來的性能消耗,這樣的話頻繁的創建線程和銷燬線程就得不償失了,我們可以將任務用Runnable的方式傳入線程池中,交給線程池來維護線程。
  • 3 從擴展性來講,我們直到Java不支持多繼承,但是支持多實現,如果我們自定義了Thread類,已經繼承了Thread類,想要再繼承其它類就不可以了,限制了它在未來的擴展性。
  • 綜上所述Runnable方式更加優於Thread方式實現多線程。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章