Android中的進程和線程
- Android中的一個應用程序一般就對應著一個進程,多進程的情況可以參考Android 多進程通信之幾個基本問題
- Android中更常見的是多線程的情況,一個應用程序中一般都有包括UI線程等多個線程。Android中規定網絡訪問必須在子線程中進行,而操作更新UI則只能在UI線程。
- 常見的網絡請求庫,如OkHttp、Volly等都為我們封裝好了線程池,所以我們在進行網絡請求時一般不是很能直觀地感受到創建線程以及切換線程的過程。
- 線程是一種很寶貴的資源,要避免頻繁創建銷毀線程,一般推薦用線程池來管理線程。
線程的狀態
線程可能存在6種不同的狀態:新創建(New)、可運行(Runnable)、阻塞狀態(Blocked)、等待狀態(Waiting)、限期等待(Timed Waiting)、終止狀態(Terminated)
- 新創建(New):創建后但還未啟動的線程(還沒有調用start方法)處于這種狀態
- 可運行(Runnable):一旦調用了start方法,線程就處于這種狀態。需要注意的是此時線程可能正在執行,也可能在等待CPU分配執行的時間
- 阻塞狀態(Blocked):表示線程被鎖阻塞,等待獲取到一個排他鎖。在程序等待進入同步區域時,線程將進入這種狀態
-
等待狀態(Waiting):處于這種狀態的線程不會被分配CPU執行時間,它們要等待被其他線程顯示地喚醒。調用以下方法會讓線程進入這種狀態:
- 沒有設置Timeout參數的Object.wait()方法
- 沒有設置Timeout參數的Thread.join()方法
-
限期等待(Timed Waiting):與等待狀態(Waiting)不同的是,處于這種狀態的線程不需要等待其它線程喚醒,在一定時間之后會由系統喚醒。調用以下方法會讓線程進入這種狀態:
- Thread.sleep()方法
- 設置了Timeout參數的Object.wait()方法
- 設置了Timeout參數的Thread.join()方法
-
終止狀態(Terminated):表示線程已經執行完畢。導致線程終止有2種情況:
- 線程的run方法執行完畢,正常退出
- 因為一個沒有捕獲的異常而終止了run方法
創建線程
創建線程一般有如下幾種方式:繼承Thread類;實現Runnable接口;實現Callable接口
- 繼承Thread類,重寫run方法
public class TestThread extends Thread {@Overridepublic void run() {System.out.println("Hello World");}public static void main(String[] args) {Thread mThread = new TestThread();mThread.start();}
}
- 實現Runnable接口,并實現run方法
public class TestRunnable implements Runnable {@Overridepublic void run() {System.out.println("Hello World");}public static void main(String[] args) {TestRunnable mTestRunnable = new TestRunnable();Thread mThread = new Thread(mTestRunnable);mThread.start();}
}
-
實現Callable接口,重寫call方法
- Callable可以在任務接受后提供一個返回值而Runnable不行
- Callable的call方法可以拋出異常,Runnable的run方法不行
- 運行Callable可以拿到一個Future對象,表示計算的結果,通過Future的get方法可以拿到異步計算的結果,不過當前線程會阻塞。
public class TestCallable {public static class MyTestCallable implements Callable<String> {@Overridepublic String call() throws Exception {//call方法可以提供返回值,而Runnable不行return "Hello World";}}public static void main(String[] args) {MyTestCallable myTestCallable = new MyTestCallable();//手動創建線程池ExecutorService executorService = new ThreadPoolExecutor(1,1,0L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(10));//運行callable可以拿到一個Future對象Future future = executorService.submit(myTestCallable);try {//等待線程結束,future.get()方法會使當前線程阻塞System.out.println(future.get());} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}
}
- 以上三種方式就是常見的創建線程的方式。推薦使用實現Runnable接口的方法。
線程中斷
- 當一個線程調用interrupt方法時,線程的中斷標識為將被設置成true
- 通過Thread.currentThread().isInterrupted()方法可以判斷線程是否應該被中斷
- 可以通過調用Thread.interrupted()對中斷標志位進行復位(設置為false)
- 如果一個線程處于阻塞狀態,線程在檢查中斷標志位時如果發現中斷標志位為true,則會在阻塞方法處拋出InterruptedException異常,并且在拋出異常前會將中斷標志位復位,即重新設置為false
- 不要在代碼底層捕獲InterruptedException異常后不做處理
同步的幾種方法
同步的方式一般有如下3種:volatile關鍵字、synchronized關鍵字、重入鎖ReentrantLock
volatile關鍵字
- volatile關鍵字實現多線程安全關鍵在于它的可見性特性,但它需要滿足一些條件才能保證線程安全,具體可以查看文章深入理解Java虛擬機(八)之Java內存模型
- 在用volatile關鍵字來實現多線程安全時需要注意volatile不保證原子性,也就是不能用于一些自增、自減等操作,也不能用于一些不變式中,自增、自減比較好理解,下面看看不變式的情況
public class VolatileTest {private volatile int lower,upper;public int getLower() {return lower;}public void setLower(int value) {if (value > upper) {throw new IllegalArgumentException();}this.lower = value;}public int getUpper() {return upper;}public void setUpper(int value) {if (value < lower) {throw new IllegalArgumentException();}this.upper = value;}
}
- 上面的例子中,如果初始值是(0,5),線程A調用setLower(4),線程B調用setUpper(3),顯然最后結果就會變成(4,3)了
- volatile使用的場景常見的有作為狀態標志以及DCL單例模式
synchronized關鍵字和重入鎖ReentrantLock
- synchronized關鍵字比較常見,可以用于同步方法也可以用于同步代碼塊,一般推薦用同步方法,同步代碼塊的安全性不高。
- 重入鎖ReentrantLock相比synchronized提供了一些獨有的特性:可以綁定多個解鎖的條件Condition、可以實現公平鎖、可以設置放棄等待獲取鎖的時間。
public class ReentrantLockTest {private Lock mLock = new ReentrantLock();//true,表示實現公平鎖<!--private Lock mLock = new ReentrantLock(true);-->private Condition condition;private void thread1() throws InterruptedException{mLock.lock();try {condition = mLock.newCondition();condition.await();System.out.println("thread1:Hello World");}finally {mLock.unlock();}}private void thread2() throws InterruptedException{mLock.lock();try {System.out.println("thread2:Hello World");condition.signalAll();}finally {mLock.unlock();}}
}
- 一個ReentrantLock有多個相關的Condition,調用Condition的await方法會讓當前線程進入該條件的等待集并阻塞,直到另一個線程調用了同一個條件的signalAll方法激活因為這個條件而進入阻塞的所有線程
- 一般線程同步用得比較多的還是synchronized同步方法和一些java.util.concurrent包提供的一些類
如何安全的終止線程
雖然我們一般都是利用線程池來管理線程而不會直接顯示地創建線程,但是作為線程相關知識的一部分,我們還是要了解如何安全地終止一個線程。
要安全地終止一個線程,一般有2種方法:中斷和標志位
(1)利用中斷來終止線程
Thread thread = new Thread(new Runnable() {@Overridepublic void run() {while (!Thread.currentThread().isInterrupted()) {//do something}}
});//當我們調用Thread的interrupt方法時,線程就會退出循環停止了。
thread.interrupt();
(2)通過標志位
private static class MyRunnable implements Runnable {//控制線程的標志位,需要用 volatile關鍵字 private volatile boolean on = true;@Overridepublic void run() {while (on) {//do something }}public void cancel() {on = false;}
}//啟動線程
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);//終止線程
myRunnable.cancel();
歡迎關注我的微信公眾號,期待與你一起學習,一起交流,一起成長!