Android 多線程:Thread理解和使用總結(jié)

Android Thread目錄.png

一、Android中的Thread

定義:線程,可以看作是進(jìn)程的一個實體,是CPU調(diào)度和分派的基本單位,它是比進(jìn)程更小的能獨立運行的基本單位。

1.1 Thread主要函數(shù)

函數(shù)名 作用
run() 線程運行時所執(zhí)行的代碼
start() 啟動線程
sleep()/sleep(long millis) 線程休眠,進(jìn)入阻塞狀態(tài),sleep方法不會釋放鎖(其它線程不會進(jìn)入synchronized方法體或方法塊,不釋放鎖需要try/catch)
yield() 線程交出CPU,但是不會阻塞而是重置為就緒狀態(tài),不會釋放鎖
join()/join(long millis)/join(long millis,int nanoseconds) 線程插隊,當(dāng)該子線程執(zhí)行完畢后接著執(zhí)行其它
wait() 進(jìn)入阻塞狀態(tài),釋放鎖(其它線程可以進(jìn)入synchronized方法體或方法塊,釋放鎖不需要try/catch)
interrupt() 中斷線程,注意只能中斷阻塞狀態(tài)的線程
getId() 獲取當(dāng)前線程的id
getName()/setName() 獲取和設(shè)置線程的name
getPriority()/setPriority() 獲取和設(shè)置線程的優(yōu)先級,范圍1-10,默認(rèn)是5
setDaemon()/isDaemo() 設(shè)置和獲取是否守護(hù)線程
currentThread() 靜態(tài)函數(shù)獲取當(dāng)前線程

1.2 Thread的幾種狀態(tài)

  • 新建狀態(tài)(new):實例化之后進(jìn)入該狀態(tài);
  • 就緒狀態(tài)(Runnable):線程調(diào)用start()之后就緒狀態(tài)等待cpu執(zhí)行,注意這時只是表示可以運行并不代表已經(jīng)運行;
  • 運行狀態(tài)(Running):線程獲得cpu的執(zhí)行,開始執(zhí)行run()方法的代碼;
  • 阻塞狀態(tài)(Blocked):線程由于各種原因進(jìn)入阻塞狀態(tài):join()、sleep()、wait()、等待觸發(fā)條件、等待由別的線程占用的鎖;
  • 死亡狀態(tài)(Dead):線程運行完畢或異常退出,可使用isAlive()獲取狀態(tài)。

二、Android中Thread的使用

這里寫出三種使用方式:
1. 繼承Thread,重寫run()方法。
使用時直接new并且start()。(不知道什么時候run()方法變成重寫了,以前可是沒有 @Override)

    public class MyThread extends Thread{

        @Override
        public void run() {
            super.run();
            // do something
        }
    }
    // Thread使用
    public void goThread(){
        new MyThread().start();
    }

2. 實現(xiàn)Runnable,重寫run()方法來執(zhí)行任務(wù)。

    public class MyRunnable implements Runnable{

        @Override
        public void run() {
            // do something
        }
    }
    new Thread(new MyRunnable()).start();

另一種啟動方式

    new Thread(new Runnable() {
        @Override
        public void run() {
            
        }
    }).start();

3. 通過Handler啟動線程。
首先定義好Handler和Runnable :

    private int count = 0;
    private Handler mHandler = new Handler();

    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            Log.i("download",Thread.currentThread().getName()+":"+count);
            count ++;
            mHandler.postDelayed(runnable,1000); // 執(zhí)行后延遲1000毫秒再次執(zhí)行,count已++
        }
    };

然后使用mHandler的post()方法執(zhí)行線程。當(dāng)上方的Runnable執(zhí)行后里面定義了mHandler.postDelayed(runnable,1000);開啟延遲1000毫秒后再次執(zhí)行。

  findViewById(R.id.btn_download).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mHandler.post(runnable); // handler運行runnable
        }
    });

三、Thread使用其它問題

3.1 如何終止線程

1. 使用boolean變量作為標(biāo)記
如下,如果變量stop為true則跳出run()方法。關(guān)鍵字volatile表示這個變量隨時都有可能發(fā)生變化,主要是表示同步,也就是同一時刻只能由一個線程來修改該變量的值。

public class ThreadStopTest extends Thread {

    public volatile boolean stop = false;
    @Override
    public void run() {
        super.run();
        while (!stop){
            // thread runing
        }
    }
    
}

2. 使用interrupt()
該方法可以用來中斷進(jìn)程,然后就可以終止線程,使用該方法分兩種情況,線程正常運行狀態(tài)阻塞狀態(tài)

(1) 線程正常運行狀態(tài)
要注意的是interrupt()方法只是中斷線程而不是結(jié)束線程,在線程正常運行狀態(tài)下使用該方法是不能結(jié)束線程的,正確的做法是判斷線程是否被中斷來決定是否執(zhí)行代碼。這種方法類似于自定義標(biāo)記??匆幌率纠?/p>

...
    // 定義開始和結(jié)束線程的方法,與按鈕綁定
    public void goThread(){
        if(null == myThread){
            myThread = new MyThread();
        }
        myThread.start();
    }
    private void stopThread() {
        if(null != myThread && myThread.isAlive()){
            myThread.interrupt();
            myThread = null;
        }
    }

    public class MyThread extends Thread{
        @Override
        public void run() {
            super.run();
            int i = 0;
            // 判斷狀態(tài),如果被打斷則跳出并將線程置空
            while (!isInterrupted()){
                i++;
                Log.i("thread",Thread.currentThread().getName()+":Running()_Count:"+i);
            }
        }
    }

(2) 線程阻塞狀態(tài)
先看下面的例子,重新寫了一個SleepThread,開啟和停止的代碼省略。這個線程里每間隔1s循環(huán)增加i的值并打印。

    public class SleepThread extends Thread{
        @Override
        public void run() {
            super.run();
            int i = 0;
            while(true){
                try {
                    i++;
                    Thread.sleep(1000);
                    Log.i("thread",Thread.currentThread().getName()+":Running()_Count:"+i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Log.i("thread",Thread.currentThread().getName()+"異常拋出,停止線程");
                    break;
                }
            }

        }
    }

當(dāng)我開始和終止該線程后Log如下:

10-14 19:46:57.562 32024-32227/com.sky.androidthreadapp I/thread: Thread-16142:Running()_Count:1
10-14 19:46:58.562 32024-32227/com.sky.androidthreadapp I/thread: Thread-16142:Running()_Count:2
...
10-14 19:47:01.564 32024-32227/com.sky.androidthreadapp I/thread: Thread-16142:Running()_Count:5
10-14 19:47:01.931 32024-32227/com.sky.androidthreadapp I/thread: Thread-16142異常拋出,停止線程

可以看到調(diào)用interrupt()方法后拋出InterruptedException異常,同時break跳出循環(huán)達(dá)到停止跑代碼的作用。原理是當(dāng)線程進(jìn)入阻塞狀態(tài)時,調(diào)用interrupt()方法會拋出異常,利用這個異常來跳出循環(huán)。
(3) 兩種狀態(tài)一起處理
此外,把兩種狀態(tài)的處理結(jié)合在一起是比較好的,這樣既可以及時判斷線程狀態(tài)又可以捕獲異常來跳出循環(huán)。

    public class SleepThread extends Thread{
        @Override
        public void run() {
            super.run();
            int i = 0;
            while(!isInterrupted()){  // 判斷線程是否被打斷
                try {
                    i++;
                    Thread.sleep(1000);
                    Log.i("thread",Thread.currentThread().getName()+":Running()_Count:"+i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Log.i("thread",Thread.currentThread().getName()+"異常拋出,停止線程");
                    break;// 拋出異常跳出循環(huán)
                }
            }

        }
    }

3. 使用stop()方法終止線程
恕我直言,這個方法就忽略吧,這個相當(dāng)于強制關(guān)機,萬一數(shù)據(jù)丟失得不償失。

3.2 線程安全與線程同步

1. 什么是線程安全問題
簡單地說,線程安全問題是指多個線程訪問同一代碼或數(shù)據(jù),造成結(jié)果和數(shù)據(jù)的錯亂或與期望的結(jié)果不同所產(chǎn)生的問題。
2. 如何解決線程安全問題

基本上所有的并發(fā)模式在解決線程安全問題的問題上,都采用“序列化訪問臨界資源”的方案,即在同一時刻只能有一個線程訪問臨界資源(多個線程可能同時訪問的數(shù)據(jù)或資源),也稱同步互斥訪問。

(1) synchronized 關(guān)鍵字,保證同時刻只有一個線程進(jìn)入該方法或者代碼塊,使用方式(Tips:java中有很多方式來實現(xiàn)線程同步,我們常用的synchronized是效率最低的...但是它方便啊):

  • 線程run()方法中要執(zhí)行的代碼方法添加synchronized關(guān)鍵字,注意要添加在方法的返回值前。
   int count = 100;
   private synchronized void count() {
        if (count > 0) {
            Log.e(TAG, Thread.currentThread().getName() + "--->" + count--);
        } else {
            isRunning = false;
        }
    }
  • 同步代碼塊的形式使用。
  private void count() {
        synchronized (this) {
            if (count > 0) {
                Log.e(TAG, Thread.currentThread().getName() + "--->" + count--);
            } else {
                isRunning = false;
            }
        }
    }

(2) 特殊域變量volatile修飾變量:告訴虛擬機該變量隨時可能更新,因此使用時每次都會重新計算,而不是使用寄存器的值。volatile不會提供任何原子操作,它也不能用來修飾final類型的變量。(不能完全保證線程安全)

private volatile int count = 1000;

(3)使用重入鎖實現(xiàn)線程同步。

  • ReentrantLock() : 創(chuàng)建一個ReentrantLock實例
  • lock() :獲得鎖
  • unlock() : 釋放鎖
    private void count() {
        lock.lock();
        if (count > 0) {
            Log.e(TAG, Thread.currentThread().getName() + "--->" + count--);
        } else {
            isRunning = false;
        }
        lock.unlock();
    }

(4)ThreadLocal管理變量。如果一個變量使用ThreadLocal進(jìn)行管理,每一個使用該變量的線程都會獲得該變量的副本,副本之間相互獨立,所以每個線程都可以修改變量而不會對其它線程造成影響。

    private static ThreadLocal<Integer> number = new ThreadLocal<Integer>(){
        // 重寫方法,設(shè)置默認(rèn)值
        @Override
        protected Integer initialValue() {
            return 1;
        }
        // 自定義方法設(shè)置變量值
        public void saveNumber(int newNumber){
            number.set(number.get() + newNumber);
        }
        // 自定義方法獲取變量值
        public int getNumber(){
            return number.get();
        }
    };

相關(guān)文章:
Android多線程:理解和簡單使用總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容