Android 線程與線程池 Thread&ThreadPool

1. 線程Thread

學過計算機的人都知道,什麼是Thread。對於計算機來說,Thread是最小的執行單元。做過Android開發的人都知道,我們開發的App至少存在一個線程,那就是主線程。那麼如果我們想要執行耗時的任務的話,我們是不能在主線程做的,因爲這樣會引起ANR。此時,我們就要開啓一個新的線程來做我們的任務,我們可以稱之爲工作線程。

1.1 開啓新的線程

那麼,我們應該如何在新線程中執行自己的任務呢?我們有兩種方法。
1. 繼承Thread類並覆蓋其run()方法
2. 使用Runnable作爲參數實例化Thread

1.1.1 繼承Thread類並覆蓋其run()方法

一個簡單的例子如下,

    class MyThread extends Thread{
        @Override
        public void run() {
            // do some thing
            super.run();
        }
    }

我們就可以實例化一個線程MyThread thread = new MyThread.

1.1.2 使用Runnable作爲參數實例化Thread

Runnable是什麼呢?其實Runable只是一個接口,其定義了一個函數run(),這個函數就是線程執行的內容。當我們使用的時候,我們需要實現Runnable接口,將需要做的工作代碼放在run()方法中。
簡單的一個例子如下,
首先我們實現Runnable接口

    class MyRunnable implements Runnable{
        @Override
        public void run() {
            //do some thing
        }
    }

然後我們可以這樣實例化一個Thread

        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);

更簡單一點,我們可以這樣,

        Thread thread = new Thread(new MyRunnable());

或者,我們可以將Runnable接口匿名實現,這樣,

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                //do some thing
            }
        });

上邊三種代碼原理其實是一樣的哈。都是將Runnable的實現(也就是run()方法的實現啦)傳給Thread。

1.2 執行新的線程

上面講了兩種開啓新的線程,那麼我們應該怎麼讓線程開始執行呢?我們可以通過調用Thread.start()方法來執行線程。

1.3 如何停止線程

上面我們介紹了,如何新建一個線程,並執行。那麼,我們就會想,如果一個線程正在執行中,我們不想讓他繼續執行了,那麼我們有什麼辦法讓它停止呢?我們在Developer上發現,Thread類有stop方法的,但是在API 1的時候,就被棄用,它是這樣解釋的,

because stopping a thread in this manner is unsafe and can leave your application and the VM in an unpredictable state.

當我們使用stop()停止一個線程是不安全的,可以造成App或虛擬機不可控的狀態。
那是不是我們就沒有辦法停止一個線程了?
首先,我們知道,當一個線程的run()方法執行完後,該線程會自動關閉。那麼,我們就可以在run()方法內部設置一個標誌位,判斷是否應該執行還是退出,比如這樣,

    class MyThread extends Thread {
        private boolean stop = false;

        public void stopThread() {
            this.stop = true;
        }

        @Override
        public void run() {
            while (!stop) {
                // do some thing
            }
            super.run();
        }
    }

2. 線程池ThreadPool

上面我們介紹了Thread,考慮這樣的一個場景,我們需要100個(甚至更多)的線程來執行不同的任務,那麼就涉及到100個線程的新建、調用和銷燬。這樣給處理器或內容帶來了較大的負擔。那麼,有沒有什麼方法可以解決該問題呢?

線程池就是解決該問題的。什麼是線程池呢?通俗的理解就是,存放線程的池子。Developer這樣提到,

Thread pools address two different problems: they usually provide improved performance when executing large numbers of asynchronous tasks, due to reduced per-task invocation overhead, and they provide a means of bounding and managing the resources, including threads, consumed when executing a collection of tasks.

兩個好處,1. 可以減少多個線程帶來的負擔。2. 提供了管理資源(包括線程)的方法。

2.1 新建線程池

Andorid並沒有提供關於線程池的構造函數,而是提供了工廠模式,封裝在Executors類中。我們可以這樣得到線程池,

2.1.1 newCachedThreadPool()

ExecutorService threadPool = Executors.newCachedThreadPool();

上邊的代碼是得到一個緩衝線程池,什麼是緩衝線程池呢,就是在必要的時候,會新建一個新的線程加入該線程池,什麼是必要的時候呢?就是該線程池的線程不能複用的時候(也就是當線程池中的所有線程都是在運行的時候),否則,就會複用線程。

2.2.2 newFixedThreadPool

ExecutorService threadPool = Executors.newFixedThreadPool(2);

上邊是得到一個固定個數的線程池,當我們要執行的任務個數大於該固定值時,其他任務就會等待,直到該線程中的某個任務執行完,然後執行等待的任務。

2.2.3 newSingleThreadExecutor

ExecutorService threadPool = Executors.newSingleThreadExecutor();

上邊的代碼是得到一個單一線程的線程池。當我們向線程池添加任務時,任務會按順序依次執行。

2.2 將線程加入到線程池

2.2.1 execute

上邊介紹瞭如何得到不同的線程池,那麼我們如何將線程加入到線程池呢?其提供了execute方法,我們可以將類型爲Runnable的實例傳進來。Runnable就是我們上邊提到的接口啦。比如,我們可以這樣,

threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Log.i(TAG, "current Thread id: " + Thread.currentThread().getId());
                }
            });

傳入的時候,我們要複寫其run()方法,也就是將我們要執行的任務代碼放入到run()方法中。

2.2.2 submit

ThreadPool同時還提供了submit方法,我們可以將Runnable或Callable傳進來,其同時還有返回值,類型爲Future

2.3 如何管理加入線程池的任務

2.2節 我們介紹瞭如何將任務加入到線程池,那麼問題來了,如果我們將很多任務加入到線程池,然後想取消其中的任務,那麼怎麼辦呢?

ExecutorService接口提供了兩個函數shutdown()和shutdownNow(),其中shutdown()是關閉向線程池加入任務,但是已經加入的則會繼續執行完畢。shutdownNow是關閉線程池中等待執行的任務,已經在執行的則會強行中斷。

舉個例子,我們新建一個單線程池,然後加入10個任務,每個任務休眠1秒鐘,由於是單線程池,那麼加入的10個任務就會依次在線程池中執行。我們在線程池執行任務的時候,分爲調用shutdownshutdownNow,然後看一下結果。

ExecutorService threadPool = Executors.newSingleThreadExecutor();
        idx = 0;
        for (int i = 0; i < 10; i++) {
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    idx++;
                    Log.i(TAG, "Thread[" + idx + "] start, id: " + Thread.currentThread().getId());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Log.i(TAG, "Thread[" + idx + "]  end, id: " + Thread.currentThread().getId());
                }
            });
        }

調用shutdown的運行截圖如下,
shutdown的運行截圖
由截圖,我們可以看到,雖然我們在Thread5開始執行的時候,調用了shutdown,但是並沒有影響已經加入到線程池的任務,在調用shutdown前已經加入的任務,將會繼續執行完畢。但是調用shutdown後,並不能向線程池加入新的任務。

調用shutdownNow的運行截圖如下,
shutdownNow的運行截圖
由截圖,我們可以看到,我們在Thread5開始的時候,調用shutdownNow,我們發現,Thread6-10並沒有執行,而且我們還發現,Thread5被中斷了。

發佈了31 篇原創文章 · 獲贊 15 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章