1. 程式人生 > >Android 多線程編程初探

Android 多線程編程初探

pbo evel interface thread service requested 返回 starting 銷毀對象

Android 中的多線程其實就是 Java SE 中的多線程,只是為了方便使用,android 封裝了一些類,如 AsyncTask、HandlerThread 等,在日常的開發過程中,我們往往需要去執行一些耗時的操作,例如發起網絡請求,考慮到網速等其他外在的因素,服務器可能不會立刻響應我們的請求,如果不將這條操作放到子線程中去執行,就會造成主線程被阻塞,今天我們就從多線程的基礎來一起探討

一、線程的基本用法

對於 Andorid 多線程來說我們最新接觸到的就是 Thread 和 Runnable 通常我們如下來啟動一個新的線程

1)繼承自 Thread 來創建子線程

定義一個線程只需要新建一個類繼承自 Thread,然後重寫父類的 run 方法即可

[java] view plain copy
  1. /**
  2. * 繼承 Thread 創建子線程
  3. */
  4. class MyThread extends Thread {
  5. @Override
  6. public void run() {
  7. //處理具體的邏輯
  8. }
  9. }


那麽如何啟動這個線程呢?只需要 new 出 MyThread 的實例,然後調用它的 start() 方法,這樣 run() 方法中的代碼就會運行在子線程,如下:

[java] view plain copy
  1. new MyThread().start();

2)實現 Runnable 接口來創建子線程

繼承方式耦合性高,更多時候我們會選擇實現 Runnable 接口的方式來實現一個子線程,如下:

[java] view plain copy
  1. /**
  2. * 實現 Runnable 接口創建子線程
  3. */
  4. class MyThread implements Runnable {
  5. @Override
  6. public void run() {
  7. //處理具體的邏輯
  8. }
  9. }


如果使用這種寫法,啟動線程的方法也需要相應的改變,如下:

[java]
view plain copy
  1. MyThread myThread = new MyThread();
  2. new Thread(myThread).start();


Thread 構造函數接受一個 Runnable 參數,我們 new 出的 MyThread 正是一個實現了 Runnable 接口的對象,所以可以直接將它傳入到 Thread 的構造函數裏,接著就和上面的一樣了,調用 Thread 的 start() 方法,run() 方法中的代碼就會在子線程當中運行了

3)直接使用匿名類來創建子線程

[java] view plain copy
  1. new Thread(new Runnable() {
  2. @Override
  3. public void run() {
  4. //處理具體的邏輯
  5. }
  6. }).start();


如果你不想再定義一個類去實現 Runnable 接口,也可以使用如上匿名類的方式來實現,這種實現方式也是我們最常用到的

以上這些就是我們來創建子線程時使用的不方式,基本和 Java 中一樣

4)Thread 和 Runnable 的區別

實際上 Thread 也是一個 Runnable,它實現了 Runnable 接口,在Thread類中有一個 Runnable 類型的 target字段,代表要被執行在這個子線程中的任務,代碼如下:

Thread 部分源碼:

[java] view plain copy
  1. public class Thread implements Runnable {
  2. /* What will be run. */
  3. private Runnable target;
  4. /* The group of this thread */
  5. private ThreadGroup group;
  6. private String name;
  7. /*
  8. * The requested stack size for this thread, or 0 if the creator did
  9. * not specify a stack size. It is up to the VM to do whatever it
  10. * likes with this number; some VMs will ignore it.
  11. */
  12. private long stackSize;
  13. /**
  14. * 初始化 Thread 並且將Thread 添加到 ThreadGroup 中
  15. *
  16. * @param g
  17. * @param target
  18. * @param name
  19. * @param stackSize
  20. */
  21. private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
  22. java.lang.Thread parent = currentThread();
  23. //group 參數為空,則獲取當前線程的才線程組
  24. if (g == null) {
  25. g = parent.getThreadGroup();
  26. }
  27. this.group = g;
  28. this.name = name;
  29. //設置 target
  30. this.target = target;
  31. /* Stash the specified stack size in case the VM cares */
  32. this.stackSize = stackSize;
  33. }
  34. public Thread() {
  35. init(null, null, null, 0);
  36. }
  37. public Thread(Runnable target) {
  38. init(null, target, null, 0);
  39. }
  40. public Thread(ThreadGroup group, Runnable target) {
  41. init(group, target, null, 0);
  42. }
  43. public Thread(Runnable target, String name) {
  44. init(null, target, name, 0);
  45. }
  46. public Thread(ThreadGroup group, Runnable target, String name) {
  47. init(group, target, name, 0);
  48. }
  49. public Thread(ThreadGroup group, Runnable target, String name,
  50. long stackSize) {
  51. init(group, target, name, stackSize);
  52. }
  53. /**
  54. * 啟動一個新的線程,如果target 不為空則執行 target 的 run 函數
  55. * 否者執行當前對象的run()方法
  56. */
  57. public synchronized void start() {
  58. //調用 nativeCreate 啟動新線程
  59. nativeCreate(this, stackSize, daemon);
  60. started = true;
  61. }
  62. @Override
  63. public void run() {
  64. if (target != null) {
  65. target.run();
  66. }
  67. }
  68. }


上面是 Thread 的部分源碼,我們看到其實 Thread 也實現了 Runnable 接口,最終被線程執行的是 Runnable,而非 Thread,Thread 只是對 Runnable 的包裝,並且通過一些狀態對 Thread 進行管理與調度,Runnable 定義了可執行的任務它只有一個無返回值的 run() 函數,如下:

[java] view plain copy
  1. public interface Runnable {
  2. /**
  3. * When an object implementing interface <code>Runnable</code> is used
  4. * to create a thread, starting the thread causes the object‘s
  5. * <code>run</code> method to be called in that separately executing
  6. * thread.
  7. * <p>
  8. * The general contract of the method <code>run</code> is that it may
  9. * take any action whatsoever.
  10. *
  11. * @see java.lang.Thread#run()
  12. */
  13. public abstract void run();
  14. }


當啟動一個線程時,如果 Thread 的 Target 不為空時,則會在子線程中執行這個 target 的 run() 函數,否則虛擬機就會執行該線程自身的 run() 函數

二、線程的 wait、sleep、join、yield

Thread 的基本用法相對來說比較簡單,通常就是復寫 run() 函數,然後調用線程的 start() 方法啟動線程,接下來我們來看看 wait、sleep、join、yield 的區別

1)wait

當一個線程執行到 wait() 方法時,它就進入到一個和該對象相關的等待池中,同時釋放對象的機鎖,使得其它線程可以訪問,用戶可以使用 notify、nitifyAll 或者指定睡眠時間來喚醒當前等待池中的線程,註意:wait()、notify()、notifyAll() 必須放在 Synchronized 塊中,否則會拋出異常

2)sleep

該函數是 Thread 的靜態函數,作用是使調用線程進入睡眠狀態,因為 sleep() 是 Thread 類的靜態方法,因此它不能改變對象鎖,所以當在一個 Synchronized 塊中調用 sleep() 方法時,線程雖然休眠了,但是對象的鎖並沒有被釋放,其它線程無法訪問這個對象,即使睡眠也持有對象的鎖

3)join

等待目標線程執行完之後再繼續執行

4)yield

線程禮讓,目標線程由運行狀態轉換為就緒狀態,也就是讓出執行權限,讓其它線程可以優先執行,但其他線程能否優先執行未知

三、線程池

當我們需要頻繁的創建多個線程進行耗時操作時,每次都經過 new Thread 實現並不是一種好的方式,每次 new Thread 新建和銷毀對象的性能較差,線程缺乏統一管理,可能無限制新建線程,相互之間競爭,可能占用過多系統資源導致死鎖,並且缺乏定時執行,定期執行,線程中斷等功能,Java 提供了 4 種線程池,它能夠有效的管理,調度線程,避免過多的浪費系統資源,它的優點如下:

1)重用存在的線程,減少對象創建,銷毀的開銷

2)可有效控制最大並發線程數,提高系統資源的使用率,同時避免過多的資源競爭,避免堵塞

3)提供定時執行、定期執行、單線程、並發數控制等功能

簡單來說,線程池原理簡單的解釋就是會創建多個線程並進行管理,提交給線程的任務會被線程池指派給其中的線程進行執行,通過線程池的統一調度、管理使得多線程使用更加簡單、高效,如下圖:

技術分享

線程池都實現了 ExecutorService 接口,改接口定義了線程池需要實現的接口,它的實現有 ThreadPoolExecutor 和 ScheduledThreadPoolExecutor,ThreadPoolExecutor 也就是我們運用最多的線程池實現,ScheduledThreadPoolExecutor 用於周期性執行任務,通常我們都不會直接通過 new 的形式來創建線程池,由於創建參數過程相對復雜一些,因此 JDK 給我們提供了一個 Executor 工廠類來簡化這個過程

線程池的使用準則:

1)不要對那些同步等待的其他任務結果的任務排隊,這可能會導致死鎖,在死鎖中所有所有線程都被一些任務所占用,這些任務依次等待排隊任務的結果,而這些任務又無法執行,因為所有的線程處於忙碌狀態

2)理解任務,要有效的調整線程池的大小,你需要理解正在排隊的任務以及它們正在做什麽

3)調整線程池的大小基本上就是避免兩類錯誤,線程太少或線程太多,幸運的是對於大多數應用程序來說,太多和太少之間的余地相當寬

今天介紹的基本都是一些理論的東西,有的看起來可能沒勁,大家就當了解一下吧,又到周五了,祝大家周末愉快

參考:郭神第一行代碼,Android 進階

Android 多線程編程初探