Android多線程筆記

消息機制

處理消息的手段–Handler,Looper與MessageQueue

tips:

  1. 子線程無法更新UI,所以需要通過Handler將一個消息Post到UI線程中(該Handler必須在主線程中創建),
    爲什麼?
    每個Handler都會關聯一個消息隊列,消息隊列被封裝在Looper中,每個Looper又回關聯一個線程(Looper通過ThreadLocal封裝),最終就等於每個消息隊列會關聯一個線程。

  2. Handler就是一個消息處理器,將消息投遞給消息隊列,然後再由對應的線程從消息隊列中逐個取出消息,並且執行。默認情況下,消息隊列只有一個,即主線程的消息隊列,這個消息隊列是在ActivityThread.main方法中創建的,通過Looper.prepareMainLooper()來創建,最後執行Looper.loop()來啓動消息循環。

  3. 那麼Handler是如何關聯消息隊列以及線程的呢?
    Handler會在內部通過Looper.myLooper()來獲取Looper對象,並且與之關聯,最重要的就是消息隊列

  4. 消息隊列通過Looper與線程關聯上,Handler與Looper關聯;
    Handler要與主線程的消息隊列關聯上,這樣handlerMessage纔會執行在UI線程,此時更新UI纔是線程安全的!

  5. 消息循環的建立是通過Looper.loop()這個方法。

Looper總結:
通過Looper.prepare()來創建Looper對象(消息隊列封裝在Looper對象中),並且保存在sThreadLoal中,然後通過Looper.loop()來執行消息循環。

Handler最終將消息追加到MessageQueue中,而Looper不斷地從
MessageQueue中讀取消息,並且調用Handler的dispatchMessage消息,
這樣消息就源源不斷地被產生,添加到MessageQueue,被handler處理,
這樣Android引用就運轉起來了

在子線程中創建Handler爲何會拋出異常?

    new Thread(){
        Handler handler = null;
        public void run(){
            handler = new Handler();
        }
    }.start();

上述代碼的問題?
在Handler源碼中,Looper對象是ThreadLocal的,每個線程都有自己的Looper,Looper可以爲空,但是當子線程中創建Handler對象時,如果Looper爲空,那麼就會拋出異常
Handler源碼中有判斷:

   public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

修改:

    new Thread(){
        Handler handler = null;
        public void run(){
            //1,爲當前線程創建Looper,綁定到ThreadLocal中
            Looper.prepare();
            handler = new Handler();
            //2,啓動消息循環
            Looper.loop();
        }
    }.start();

多線程

Android中的多線程就是JAVA中的多線程,Java中的線程詳解:http://www.cnblogs.com/riskyer/p/3263032.html
爲了方便,Android封裝了一些類,如:AsyncTask,HandlerThread等。

Runnable和Thread有什麼區別?

看Thread的源碼,Thread實現了Runnable接口,Thread裏最終被線程執行的任務是Runnable,而非Thread。
Thread只是對Runnable的包裝,並且通過一些狀態對Thread進行管理與調度。
Runnalbe接口定義了可執行的任務,它只有一個無返回值的run()函數。

線程的狀態

線程有四種狀態,任何一個線程肯定處於這四種狀態中的一種:
1) 產生(New):線程對象已經產生,但尚未被啓動,所以無法執行。
如通過new產生了一個線程對象後沒對它調用start()函數之前。
2) 可執行(Runnable):每個支持多線程的系統都有一個排程器,排程器會從線程池中選擇一個線程
並啓動它。當一個線程處於可執行狀態時,表示它可能正處於線程池中等待排排程器啓動它;
也可能它已正在執行。如執行了一個線程對象的start()方法後,線程就處於可執行狀態,
但顯而易見的是此時線程不一定正在執行中。
3) 死亡(Dead):當一個線程正常結束,它便處於死亡狀態。
如一個線程的run()函數執行完畢後線程就進入死亡狀態。
4) 停滯(Blocked):當一個線程處於停滯狀態時,系統排程器就會忽略它,不對它進行排程。
當處於停滯狀態的線程重新回到可執行狀態時,它有可能重新執行。如通過對一個線程調用wait()函數後,
線程就進入停滯狀態,只有當兩次對該線程調用notify或notifyAll後它才能兩次回到可執行狀態。

線程的wait、sleep、join和yield(面試必備)

1) wait()
讓當前的線程等待,直到其他線程調用此對象的 notify()方法或 notifyAll()方法。
使當前線程暫停執行並釋放對象鎖標示,讓其他線程可以進入synchronized數據塊,
當前線程被放入對象等待池中。當調用notify()方法後,
將從對象的等待池中移走一個任意的線程並放到鎖標誌等待池中,
只有鎖標誌等待池中線程能夠獲取鎖標誌;如果鎖標誌等待池中沒有線程,則notify()不起作用。
notifyAll()則從對象等待池中移走所有等待那個對象的線程並放到鎖標誌等待池中。
1) sleep()
使當前線程(即調用該方法的線程)暫停執行一段時間,讓其他線程有機會繼續執行,但它並不釋放對象鎖。也就是說如果有synchronized同步快,其他線程仍然不能訪問共享數據。注意該方法要捕捉異常。
2) join()
join()方法使當前線程停下來等待,直至另一個調用join方法的線程終止。線程在被激活後不一定馬上就運行,而是進入到可運行線程的隊列中。但是join()可以通過interrupt()方法打斷線程的暫停狀態,從而使線程立刻拋出InterruptedException。
3) yield()
Yield()方法是停止當前線程,讓同等優先權的線程運行。如果沒有同等優先權的線程,那麼Yield()方法將不會起作用。

線程方法 是否釋放同步鎖 是否需要在同步的代碼塊中調用 方法是否已廢棄 是否可以被中斷
sleep()
wait()
suspend 1.6廢棄 是
resume() 1.6廢棄 是
join()

擴展:http://zheng12tian.iteye.com/blog/1233638
http://dylanxu.iteye.com/blog/1322066

關於wait和notify、notifyAll的運用例子:

    public static void main(String[] args) {
        waitAndNotifyAll();
    }

    private static Object object = new Object();
    private static void waitAndNotifyAll() {
        System.out.println("主線程啓動");
        Thread thread = new WaitThread();
        thread.start();
        long startTime = System.currentTimeMillis();
        try {
            synchronized (object) {
                System.out.println("主線程等待");
                object.wait();
            }
        } catch (Exception e) {
            // TODO: handle exception
        }
        long time = System.currentTimeMillis() - startTime;
        System.out.println("主線程繼續->等待耗時:" + time + " ms");
    }

    static class WaitThread extends Thread {
        @Override
        public void run() {
            // TODO Auto-generated method stub
            try {
                synchronized (object) {
                    Thread.sleep(3000);
                    object.notifyAll();
                }
            } catch (Exception e) {
                // TODO: handle exception
            }
        }
    }

運行後的結果

主線程啓動
主線程等待
主線程繼續->等待耗時:3000 ms

與多線程相關的方法–Callable、Future和FutureTask

Runnable和Callable功能大致類似,不同的是Callable是一個泛型接口,切有一個返回值爲Call()函數,而Runnable的run()函數不能講結果返回給客戶程序。Callable的聲明:

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

==可返回值的任務必須實現Callable接口,類似的,無返回值的任務必須Runnable接口==

Future提供了對Runnable或Callable任務的執行結果進行取消,查詢是否完成,獲取結果,設置結果操作,分別對應cancel,isDone,get,set函數。get方法會阻塞,直到任務返回結果;
說到底,Future只是定義了一些規範的接口,而FutureTask纔是具體實現類,FutureTask實現了RunnableFuture,而RunnableFuture實現了Runnable有實現了Future這2個接口,所以FutureTask具備了他們的能力

關於Runnable、Callable、FutureTask的運用例子:

public static void main(String[] args) {
        try {
            futureWithRunnable();
            futureWithCallable();
            futureTask();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    static ExecutorService mExecutor = Executors.newSingleThreadExecutor();

    private static void futureTask() throws InterruptedException,ExecutionException {
        // TODO Auto-generated method stub
        FutureTask<Integer> result = new FutureTask<Integer>(new Callable<Integer>() {

            @Override
            public Integer call() throws Exception {
                // TODO Auto-generated method stub
                return fibc(20);
            }
        });
        mExecutor.submit(result);
        System.out.println("FutureTask:"+result.get());
    }

    private static void futureWithCallable() throws InterruptedException,ExecutionException  {
        // TODO Auto-generated method stub
        Future<Integer> result = mExecutor.submit(new Callable<Integer>() {

            @Override
            public Integer call() throws Exception {
                // TODO Auto-generated method stub
                return fibc(20);
            }

        });
        System.out.println("Callable:"+result.get());
    }
    /**
     * runnable無返回值,所以get()的值爲null
     */
    private static void futureWithRunnable() throws InterruptedException,ExecutionException {
        //提交runnable,
        Future<?> result = mExecutor.submit(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                fibc(20);
            }
        });
        System.out.println("runnable:"+result.get());
    }
    //效率低夏的 斐波那契數列
    private static int fibc(int i) {
        // TODO Auto-generated method stub
        if (i == 0) {
            return 0;
        }
        if (i == 1) {
            return 1;
        }
        return fibc(i-1) + fibc(i-2);
    }

結果:

runnable:null
Callable:6765
FutureTask:6765

在上面的例子中,第一個結果爲null是因爲Runnable沒有回調結果,所以get的值爲null
第二個Callable那個是通過Future的get函數得到結果,
FutureTask則是一個RunnableFuture,既實現了Runnable又實現了Future,另外
還可以包裝Runnable(實際轉成Callable)和Callable,提交給ExecuteService來執行後也可以通過
返回的Future對象的get函數得到執行結果,在線程體沒有執行完成時,主線程一直阻塞等待,執行完則直接返回結果。

線程池

引子:當需要頻繁創建多個線程進行耗時操作時,每次通過new Thread實現性能很差,缺乏統一管理,可能無限制新建線程導致線程之間的競爭,可能佔用多系統資源導致死鎖,缺乏定時執行,定期執行,線程中斷等功能;

這時線程池就派上用場了,Java提供了4種線程池,它能有效管理調度線程,避免資源消耗,優點:

1,重用存在的線程,減少對象創建、銷燬的開銷;
2,可有效控制最大併發線程數、提高系統資源的使用率,同事避免過多資源競爭,避免堵塞;
3,提供定時執行、定期執行、單線程、併發數控制等功能。

image

java中的線程池擴展:http://cuisuqiang.iteye.com/blog/2019372

==線程池都實現了ExecutorService接口,該接口定義了線程池需要實現的接口,如submit、execute、shutdown等==

啓動指定數量的線程(ThreadPoolExecutor)與定時執行任務(ScheduledThreadPoolExecutor)

1,ThreadPoolExecutor 是線程池的實現之一,功能是啓動指定數量的線程以及將任務添加到一個隊列中,並且將任務分發給空閒的線程。
2,ScheduledThreadPoolExecutor在我們需要定時執行一些任務的場景使用,通過Executors和newScheduledThreadPool函數就可方便地創建定時執行任務的線程池。

擴展:
ThreadPoolExecutor詳解 http://blog.chinaunix.net/uid-20577907-id-3519578.html
Java線程池使用說明 http://www.oschina.net/question/565065_86540
JAVA線程池的分析和使用 http://www.infoq.com/cn/articles/java-threadPool/
Java 理論與實踐: 線程池與工作隊列 http://www.ibm.com/developerworks/cn/java/j-jtp0730/index.html

AysncTask的原理

AysncTask是解決Thread和Handler更新UI時的代碼臃腫,多任務無法精確控制等缺點而產生的,它的誕生使得創建異步任務變得更加簡單,不在需要編寫任務線程和Handler實例,相對Handler和Thread來說易於使用
==使用注意:==
- 異步任務的實例必須在UI線程中創建
- execute(Params… params)方法必須在UI線程中調用
- 不能在doInBackground(Params… params)中更改UI組件的信息
- 一個任務實例只能執行一次,如果執行第二次將會拋出異常
- 不要在程序中直接調用 onPreExecute()、onPostExecute()、doInBackgroud 和 onProgressUpdate 方法;

AysncTask的執行原理

  1. doInBackground(Params… params) 是一個抽象方法,繼承AsyncTask必須覆寫此方法。
  2. onPreExecute()、onProgressUpdate(Progress… values)、onPostExecute(Result result)、onCancelled()這幾個方法體都是空的,需要的時候可以選擇性地覆寫它們。
  3. publishProgress(Progress… values) 是final修飾的,不能覆寫,只能調用,一般都會在doInBackground(Params… params)中調用此方法來更新進度條;

實現一個簡單的AsyncTask

HandlerThread是自帶消息隊列的Thread類型,當線程

public abstract class SimpleAsyncTask<Result> {
    private static final HandlerThread ht = new HandlerThread("SimpleAsyncTask", Process.THREAD_PRIORITY_BACKGROUND);
    static{
        ht.start();
    }

    final Handler mUIHandler = new Handler(Looper.getMainLooper());
    final Handler mAsyncHandler = new Handler(ht.getLooper());

    protected void onPreExecute(){}
    protected void onPostExecute(Result result){}
    protected abstract Result doInBackground();
    public final SimpleAsyncTask<Result> execute(){
        onPreExecute();
        mAsyncHandler.post(new Runnable() {
            @Override
            public void run() {
                postResult(doInBackground());
            }
        });
        return this;
    }

    private void postResult(final Result result){
        mUIHandler.post(new Runnable() {
            @Override
            public void run() {
                onPostExecute(result);
            }
        });
    }
}

總結:
多線程編程在應用開發中隨處可見,網絡請求、IO操作等耗時操作都需要異步執行,線程池是進行異步操作的重要方式,在概念上十分簡單,並且封裝良好,基本滿足需求。自行實現一個行爲正確的線程池並不是那麼容易,需要解決死鎖、資源不足、和wait()以及notify()等複雜問題。
因此建議在Executor類族的基礎上正確的運用而不建議自定義線程池。

參考書目:《Android從小工到專家》,《Android開發藝術探索》

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