線程、多線程與線程池總結

先看幾個概念:java

線程:進程中負責程序執行的執行單元。一個進程中至少有一個線程。git

多線程:解決多任務同時執行的需求,合理使用CPU資源。多線程的運行是根據CPU切換完成,如何切換由CPU決定,所以多線程運行具備不肯定性。程序員

線程池:基本思想仍是一種對象池的思想,開闢一塊內存空間,裏面存放了衆多(未死亡)的線程,池中線程執行調度由池管理器來處理。當有線程任務時,從池中取一個,執行完成後線程對象歸池,這樣能夠避免反覆建立線程對象所帶來的性能開銷,節省了系統的資源。github

若是對線程概念不清晰的話,不妨先看看 我是一個線程 這篇文章,擬人化的故事闡述線程的工做原理。面試

● 線程

建立線程的兩種方式:算法

1、繼承Thread類,擴展線程。

class DemoThread extends Thread {

    @Override
    public void run() {
        super.run();
        // Perform time-consuming operation...
    }
}

DemoThread t = new DemoThread();
t.run();
  • 繼承Thread類,覆蓋run()方法。
  • 建立線程對象並用start()方法啓動線程。

面試題

  • 1)線程和進程有什麼區別?

一個進程是一個獨立(self contained)的運行環境,它能夠被看做一個程序或者一個應用。而線程是在進程中執行的一個任務。線程是進程的子集,一個進程能夠有不少線程,每條線程並行執行不一樣的任務。不一樣的進程使用不一樣的內存空間,而全部的線程共享一片相同的內存空間。別把它和棧內存搞混,每一個線程都擁有單獨的棧內存用來存儲本地數據。緩存

  • 2)如何在Java中實現線程?

建立線程有兩種方式:安全

1、繼承 Thread 類,擴展線程。多線程

2、實現 Runnable 接口。併發

  • 3)Thread 類中的 start() 和 run() 方法有什麼區別?

調用 start() 方法纔會啓動新線程;若是直接調用 Thread 的 run() 方法,它的行爲就會和普通的方法同樣;爲了在新的線程中執行咱們的代碼,必須使用 Thread.start() 方法。

擴展

Android 系統接口 HandlerThread 繼承了 Thread,它是一個可使用 Handler 的 Thread,一個具備消息循環的線程。run()方法中經過 Looper.prepare() 來建立消息隊列,經過 Looper.loop() 來開啓消息循環。能夠在 run() 方法中執行耗時的任務,而 HandlerThread 內部建立了消息隊列外界須要經過 Handler 的方式來通知 HandlerThread 執行一個具體任務;HandlerThread 的 run() 方法是一個無限的循環,能夠經過它的 quite() 或 quitSafely() 方法來終止線程的執行;

2、實現Runnable接口。

public class DemoActivity extends BaseActivity implements Runnable {

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    Thread t = new Thread(this);
    t.start();
  }

  @Override
  public void run() {

  }
}

面試題

  • 1)用 Runnable 仍是 Thread ?

咱們都知道能夠經過繼承 Thread 類或者調用 Runnable 接口來實現線程,問題是,建立線程哪一種方式更好呢?什麼狀況下使用它?若是你知道Java不支持類的多重繼承,但容許你調用多個接口。因此若是你要繼承其餘類,固然是調用Runnable接口更好了。

  • 2)Runnable 和 Callable 有什麼不一樣?

Runnable 和 Callable 都表明那些要在不一樣的線程中執行的任務。Runnable 從 JDK1.0 開始就有了,Callable 是在 JDK1.5 增長的。它們的主要區別是 Callable 的 call() 方法能夠返回值和拋出異常,而 Runnable 的 run() 方法沒有這些功能。Callable 能夠返回裝載有計算結果的 Future 對象。

⚠注意:這面第二個面試題主要是爲了引出下面的擴展,原諒我這樣爲難人的出場。

擴展

先看一下 Runnable 和 Callable 的源碼

public interface Runnable {
  public void run();
}

public interface Callable<V> {
  V call() throws Exception;
}

能夠得出:

1)Callable 接口下的方法是 call(),Runnable 接口的方法是 run()。

2)Callable 的任務執行後可返回值,而 Runnable 的任務是不能返回值的。

3)call() 方法能夠拋出異常,run()方法不能夠的。

4)運行 Callable 任務能夠拿到一個 Future 對象,表示異步計算的結果。它提供了檢查計算是否完成的方法,以等待計算的完成,並檢索計算的結果。經過 Future 對象能夠了解任務執行狀況,可取消任務的執行,還可獲取執行結果。

可是,可是,凡事都有可是嘛…

單獨使用 Callable,沒法在新線程中(new Thread(Runnable r))使用,Thread 類只支持 Runnable。不過 Callable 可使用 ExecutorService (又拋出一個概念,這個概念將在下面的線程池中說明)。

上面又提到了 Future,看一下 Future 接口:

public interface Future<V> {

  boolean cancel(boolean mayInterruptIfRunning);

  boolean isCancelled();

  boolean isDone();

  V get() throws InterruptedException, ExecutionException;

  V get(long timeout, TimeUnit unit)
    	throws InterruptedException, ExecutionException, TimeoutException;
}

Future 定義了5個方法:

1)boolean cancel(boolean mayInterruptIfRunning):試圖取消對此任務的執行。若是任務已完成、或已取消,或者因爲某些其餘緣由而沒法取消,則此嘗試將失敗。當調用 cancel() 時,若是調用成功,而此任務還沒有啓動,則此任務將永不運行。若是任務已經啓動,則 mayInterruptIfRunning 參數肯定是否應該以試圖中止任務的方式來中斷執行此任務的線程。此方法返回後,對 isDone() 的後續調用將始終返回 true。若是此方法返回 true,則對 isCancelled() 的後續調用將始終返回 true。

2)boolean isCancelled():若是在任務正常完成前將其取消,則返回 true。

3)boolean isDone():若是任務已完成,則返回 true。 可能因爲正常終止、異常或取消而完成,在全部這些狀況中,此方法都將返回 true。

4)V get()throws InterruptedException,ExecutionException:若有必要,等待計算完成,而後獲取其結果。

5)V get(long timeout,TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException: 若有必要,最多等待爲使計算完成所給定的時間以後,獲取其結果(若是結果可用)。

看來 Future 接口也不能用在線程中,那怎麼用,誰實現了 Future 接口呢?:FutureTask。

public class FutureTask<V> implements RunnableFuture<V> {
  ...
}

public interface RunnableFuture<V> extends Runnable, Future<V> {
  void run();
}

FutureTask 實現了 Runnable 和 Future,因此兼顧二者優勢,既能夠在 Thread 中使用,又能夠在 ExecutorService 中使用。

看完上面囉哩囉嗦的介紹後,下面咱們具體看一下具體使用的示例:

Callable<String> callable = new Callable<String>() {
    @Override
    public String call() throws Exception {
        return "我的博客:sunfusheng.com";
    }
};

FutureTask<String> task = new FutureTask<String>(callable);

Thread t = new Thread(task);
t.start();

使用 FutureTask 的好處是 FutureTask 是爲了彌補 Thread 的不足而設計的,它可讓程序員準確地知道線程何時執行完成並得到到線程執行完成後返回的結果。FutureTask 是一種能夠取消的異步的計算任務,它的計算是經過 Callable 實現的,它等價於能夠攜帶結果的 Runnable,而且有三個狀態:等待、運行和完成。完成包括全部計算以任意的方式結束,包括正常結束、取消和異常。

查看具體操做代碼請參考 我的學習項目DroidStudy ,感謝您的關注。

● 多線程

多線程的概念很好理解就是多條線程同時存在,但要用好多線程確不容易,涉及到多線程間通訊,多線程共用一個資源等諸多問題。

使用多線程的優缺點:

優勢:

1)適當的提升程序的執行效率(多個線程同時執行)。

2)適當的提升了資源利用率(CPU、內存等)。

缺點:

1)佔用必定的內存空間。

2)線程越多CPU的調度開銷越大。

3)程序的複雜度會上升。

對於多線程的示例代碼感興趣的能夠本身寫Demo啦,去運行體會,下面我主要列出一些多線程的技術點。

synchronized

同步塊你們都比較熟悉,經過 synchronized 關鍵字來實現;全部加上 synchronized 的方法和塊語句,在多線程訪問的時候,同一時刻只能有一個線程可以訪問。

wait()、notify()、notifyAll()

這三個方法是 java.lang.Object 的 final native 方法,任何繼承 java.lang.Object 的類都有這三個方法。它們是Java語言提供的實現線程間阻塞和控制進程內調度的底層機制,平時咱們會不多用到的。

wait():致使線程進入等待狀態,直到它被其餘線程經過notify()或者notifyAll喚醒,該方法只能在同步方法中調用。

notify():隨機選擇一個在該對象上調用wait方法的線程,解除其阻塞狀態,該方法只能在同步方法或同步塊內部調用。

notifyAll():解除全部那些在該對象上調用wait方法的線程的阻塞狀態,一樣該方法只能在同步方法或同步塊內部調用。

調用這三個方法中任意一個,當前線程必須是鎖的持有者,若是不是會拋出一個 IllegalMonitorStateException 異常。

wait() 與 Thread.sleep(long time) 的區別

sleep():在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),該線程不丟失任何監視器的所屬權,sleep() 是 Thread 類專屬的靜態方法,針對一個特定的線程。

wait() 方法使實體所處線程暫停執行,從而使對象進入等待狀態,直到被 notify() 方法通知或者 wait() 的等待的時間到。sleep() 方法使持有的線程暫停運行,從而使線程進入休眠狀態,直到用 interrupt 方法來打斷他的休眠或者 sleep 的休眠的時間到。

wait() 方法進入等待狀態時會釋放同步鎖,而 sleep() 方法不會釋放同步鎖。因此,當一個線程無限 sleep 時又沒有任何人去 interrupt 它的時候,程序就產生大麻煩了,notify() 是用來通知線程,但在 notify() 以前線程是須要得到 lock 的。另個意思就是必須寫在 synchronized(lockobj) {…} 之中。wait() 也是這個樣子,一個線程須要釋放某個 lock,也是在其得到 lock 狀況下才可以釋放,因此 wait() 也須要放在 synchronized(lockobj) {…} 之中。

volatile 關鍵字

volatile 是一個特殊的修飾符,只有成員變量才能使用它。在Java併發程序缺乏同步類的狀況下,多線程對成員變量的操做對其它線程是透明的。volatile 變量能夠保證下一個讀取操做會在前一個寫操做以後發生。線程都會直接從內存中讀取該變量而且不緩存它。這就確保了線程讀取到的變量是同內存中是一致的。

ThreadLocal 變量

ThreadLocal 是Java裏一種特殊的變量。每一個線程都有一個 ThreadLocal 就是每一個線程都擁有了本身獨立的一個變量,競爭條件被完全消除了。若是爲每一個線程提供一個本身獨有的變量拷貝,將大大提升效率。首先,經過複用減小了代價高昂的對象的建立個數。其次,你在沒有使用高代價的同步或者不變性的狀況下得到了線程安全。

join() 方法

join() 方法定義在 Thread 類中,因此調用者必須是一個線程,join() 方法主要是讓調用該方法的 Thread 完成 run() 方法裏面的東西后,再執行 join() 方法後面的代碼,看下下面的」意思」代碼:

Thread t1 = new Thread(計數線程一);  
Thread t2 = new Thread(計數線程二);  
t1.start();  
t1.join(); // 等待計數線程一執行完成,再執行計數線程二
t2.start();

啓動 t1 後,調用了 join() 方法,直到 t1 的計數任務結束,才輪到 t2 啓動,而後 t2 纔開始計數任務,兩個線程是有嚴格的執行順序。若是 t2 的執行須要依賴於 t1 中的完整數據的時候,這種方法就能夠很好的確保兩個線程的同步性。

Thread.yield() 方法

Thread.sleep(long time):線程暫時終止執行(睡眠)必定的時間。Thread.yield():線程放棄運行,將CPU的控制權讓出。 

這兩個方法都會將當前運行線程的CPU控制權讓出來,但 sleep() 方法在指定的睡眠時間內必定不會再獲得運行機會,直到它的睡眠時間完成;而 yield() 方法讓出控制權後,還有可能立刻被系統的調度機制選中來運行,好比,執行yield()方法的線程優先級高於其餘的線程,那麼這個線程即便執行了 yield() 方法也可能不能起到讓出CPU控制權的效果,由於它讓出控制權後,進入排隊隊列,調度機制將從等待運行的線程隊列中選出一個等級最高的線程來運行,那麼它又(極可能)被選中來運行。

擴展

線程調度策略

(1) 搶佔式調度策略

Java運行時系統的線程調度算法是搶佔式的。Java運行時系統支持一種簡單的固定優先級的調度算法。若是一個優先級比其餘任何處於可運行狀態的線程都高的線程進入就緒狀態,那麼運行時系統就會選擇該線程運行。新的優先級較高的線程搶佔了其餘線程。可是Java運行時系統並不搶佔同優先級的線程。換句話說,Java運行時系統不是分時的。然而,基於Java Thread類的實現系統多是支持分時的,所以編寫代碼時不要依賴分時。當系統中的處於就緒狀態的線程都具備相同優先級時,線程調度程序採用一種簡單的、非搶佔式的輪轉的調度順序。

(2) 時間片輪轉調度策略

有些系統的線程調度採用時間片輪轉調度策略。這種調度策略是從全部處於就緒狀態的線程中選擇優先級最高的線程分配必定的CPU時間運行。該時間事後再選擇其餘線程運行。只有當線程運行結束、放棄(yield)CPU或因爲某種緣由進入阻塞狀態,低優先級的線程纔有機會執行。若是有兩個優先級相同的線程都在等待CPU,則調度程序以輪轉的方式選擇運行的線程。

以上把這些技術點總結出來,後面我會把多線程這塊的示例代碼更新到 我的學習項目DroidStudy ,感謝您的關注。

● 線程池

建立多線程不光麻煩並且相對影響系統性能,接下來讓我看看使用線程池來操做多線程。

線程池的優勢

1)避免線程的建立和銷燬帶來的性能開銷。

2)避免大量的線程間因互相搶佔系統資源致使的阻塞現象。

3}可以對線程進行簡單的管理並提供定時執行、間隔執行等功能。

再擼一擼概念

Java裏面線程池的頂級接口是 Executor,不過真正的線程池接口是 ExecutorService, ExecutorService 的默認實現是 ThreadPoolExecutor;普通類 Executors 裏面調用的就是 ThreadPoolExecutor。

照例看一下各個接口的源碼:

public interface Executor {
  void execute(Runnable command);
}

public interface ExecutorService extends Executor {
  void shutdown();
  List<Runnable> shutdownNow();
  
  boolean isShutdown();
  boolean isTerminated();
  
  <T> Future<T> submit(Callable<T> task);
  <T> Future<T> submit(Runnable task, T result);
  Future<?> submit(Runnable task);
  ...
}

public class Executors {
  public static ExecutorService newCachedThreadPool() {
    		return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, 
    						new SynchronousQueue<Runnable>());
  }
  ...
}

下面我建立的一個線程池:

ExecutorService pool = Executors.newCachedThreadPool();

Executors 提供四種線程池:

  • 1)newCachedThreadPool 是一個可根據須要建立新線程的線程池,可是在之前構造的線程可用時將重用它們。對於執行不少短時間異步任務的程序而言,這些線程池一般可提升程序性能。調用 execute() 將重用之前構造的線程(若是線程可用)。若是現有線程沒有可用的,則建立一個新線程並添加到池中。終止並從緩存中移除那些已有 60 秒鐘未被使用的線程。所以,長時間保持空閒的線程池不會使用任何資源。注意,可使用 ThreadPoolExecutor 構造方法建立具備相似屬性但細節不一樣(例如超時參數)的線程池。

  • 2)newSingleThreadExecutor 建立是一個單線程池,也就是該線程池只有一個線程在工做,全部的任務是串行執行的,若是這個惟一的線程由於異常結束,那麼會有一個新的線程來替代它,此線程池保證全部任務的執行順序按照任務的提交順序執行。

  • 3)newFixedThreadPool 建立固定大小的線程池,每次提交一個任務就建立一個線程,直到線程達到線程池的最大大小,線程池的大小一旦達到最大值就會保持不變,若是某個線程由於執行異常而結束,那麼線程池會補充一個新線程。

  • 4)newScheduledThreadPool 建立一個大小無限的線程池,此線程池支持定時以及週期性執行任務的需求。

經過 ThreadPoolExecutor 的構造函數,擼一擼線程池相關參數的概念:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, 
    	threadFactory, defaultHandler);
}
  • 1)corePoolSize:線程池的核心線程數,通常狀況下無論有沒有任務都會一直在線程池中一直存活,只有在 ThreadPoolExecutor 中的方法 allowCoreThreadTimeOut(boolean value) 設置爲 true 時,閒置的核心線程會存在超時機制,若是在指定時間沒有新任務來時,核心線程也會被終止,而這個時間間隔由第3個屬性 keepAliveTime 指定。

  • 2)maximumPoolSize:線程池所能容納的最大線程數,當活動的線程數達到這個值後,後續的新任務將會被阻塞。

  • 3)keepAliveTime:控制線程閒置時的超時時長,超過則終止該線程。通常狀況下用於非核心線程,只有在 ThreadPoolExecutor 中的方法 allowCoreThreadTimeOut(boolean value) 設置爲 true時,也做用於核心線程。

  • 4)unit:用於指定 keepAliveTime 參數的時間單位,TimeUnit 是個 enum 枚舉類型,經常使用的有:TimeUnit.HOURS(小時)、TimeUnit.MINUTES(分鐘)、TimeUnit.SECONDS(秒) 和 TimeUnit.MILLISECONDS(毫秒)等。

  • 5)workQueue:線程池的任務隊列,經過線程池的 execute(Runnable command) 方法會將任務 Runnable 存儲在隊列中。

  • 6)threadFactory:線程工廠,它是一個接口,用來爲線程池建立新線程的。

線程池的關閉

ThreadPoolExecutor 提供了兩個方法,用於線程池的關閉,分別是 shutdown() 和 shutdownNow()。

shutdown():不會當即的終止線程池,而是要等全部任務緩存隊列中的任務都執行完後才終止,但不再會接受新的任務。shutdownNow():當即終止線程池,並嘗試打斷正在執行的任務,而且清空任務緩存隊列,返回還沒有執行的任務。

面試題

1)什麼是 Executor 框架?

Executor框架在Java 5中被引入,Executor 框架是一個根據一組執行策略調用、調度、執行和控制的異步任務的框架。

無限制的建立線程會引發應用程序內存溢出,因此建立一個線程池是個更好的的解決方案,由於能夠限制線程的數量而且能夠回收再利用這些線程。利用 Executor 框架能夠很是方便的建立一個線程池。

2)Executors 類是什麼?

Executors爲Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 類提供了一些工具方法。Executors 能夠用於方便的建立線程池。

相關文章
相關標籤/搜索