【多線程與并發(fā)】Android線程基礎

  • 線程與進程
  • 線程的幾種創(chuàng)建方式
  • 線程的優(yōu)先級
  • 線程的幾種狀態(tài)與常用方法
  • 線程間消息通訊
  • 線程安全
Android Thread.png

線程與進程

  • 一個進程至少一個線程
  • 進程可以包含多個線程
  • 進程在執(zhí)行過程中擁有獨立的內(nèi)存空間作喘,而線程運行在進程內(nèi)

線程的幾種創(chuàng)建方式

  • new Thread:可復寫Thread#run方法。也可傳遞Runnable對象宏所,更加靈活惊搏。
    • 缺點:缺乏統(tǒng)一管理溪胶,可能無限制新建線程,相互之間競爭,極可能占用過多系統(tǒng)資源導致死機或oom
// 傳遞Runnable對象
1.new Thread(new Runnable(){
 ...
}).start()

//復寫Thread#run方法
2.class MyThread extends Thread{
  public void run(){
    
  }
}
new MyThread().start()
  • AysncTask:輕量級的異步任務工具類喉镰,提供任務執(zhí)行的進度回調(diào)給UI線程
    • 場景:需要知曉任務執(zhí)行的速度乖阵,多個任務串行執(zhí)行
    • 缺點:生命周期和宿主的生命周期不同步宣赔,有可能發(fā)生內(nèi)存泄漏,默認情況所有任務串行執(zhí)行
class AsyncTaskTest extends AsyncTask<String,Integer,String> {
   private static final String TAG = "AsyncTaskTest";
    @Override
    protected String doInBackground(String... params) {
        for (int i = 0; i < 10; i++) {
            publishProgress(i * 10);
        }
        return params[0];
    }

    @Override
    protected void onPostExecute(String result) {
        Log.e(TAG,"result:" + result);
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        Log.e(TAG,"onProgressUpdate:" + values[0].intValue());
    }

    public static void main(String[] args) {
        //1.子類復寫方法
        AsyncTaskTest asyncTaskTest = new AsyncTaskTest();
        asyncTaskTest.execute("execute asyncTaskTest");
           //or
        asyncTaskTest.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"execute AsyncTaskTest");

        //2.使用execute方法瞪浸,同樣串行執(zhí)行
        AsyncTask.execute(new Runnable() {
            @Override
            public void run() {
                .....
            }
        });
        //3.使用內(nèi)置THREAD_POOL_EXECUTOR線程池儒将,并發(fā)執(zhí)行
        AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
            @Override
            public void run() {

            }
        });
    }
}
  • HandlerThread:適用于主線程需要和工作線程通信,適用于持續(xù)性任務对蒲,比如輪訓的場景钩蚊,所有任務串行執(zhí)行。
    • 缺點:不會像普通線程一樣主動銷毀資源蹈矮,會一直運行下去砰逻,所以可能會造成內(nèi)存溢出。
 HandlerThread thread = new HandlerThread("concurrent-thread");
        thread.start();
        ThreadHandler handler = new ThreadHandler(thread.getLooper()) {
            @Override
            public void handleMessage(@NonNull Message msg) {
                switch ((msg.what)) {
                    case MSG_WHAT_FLAG_1:
                        break;
                }

            }
        };

 handler.sendEmptyMessage(MSG_WHAT_FLAG_1);
 thread.quitSafely();
//定義成靜態(tài)含滴,防止內(nèi)存泄漏
 static class ThreadHandler extends Handler {
        public ThreadHandler(Looper looper) {
            super(looper);
        }
    }
  • IntentService:適用于我們的任務需要跨頁面讀取任務執(zhí)行的進度诱渤,結(jié)果。比如后臺上傳頁面谈况,批量操作數(shù)據(jù)庫等勺美,任務執(zhí)行完成功后,就會自我結(jié)束碑韵,所以不需要手動stopService赡茸,這是它跟Service的區(qū)分。
class MyIntentService extends IntentService{
  @Override
  protected void onHandleIntent(@Nullable Intent intent){
    int conmmand = intent.getInt("command")
      ·····
  }
}
//啟動IntentService
context.startService(new Intent())
  • ThreadPoolExecutor:適用于快速處理大量耗時較短的任務場景
Excutors.newCachedThreadPool();//線程可復用線程池
Excutors.newFixedThreadPool();//固定線程數(shù)量的線程池
Excutors.newScheduledThreadPool();//可指定定時任務的線程池
Excutors.newSingleThreadExecutor();//線程數(shù)量為1的線程池

線程優(yōu)先級

public static void main(String[] args){
  Tread thread = new Thread();
  thread.start();
  int ui_proi = Process.getThreadPriority(0);
  int th_proi = thread.getPriority();
  
  //輸出結(jié)果
  ui_proi = 5
  th_proi = 5
}
  • 線程的優(yōu)先級具有繼承性祝闻,在某線程中創(chuàng)建的線程會繼承此線程的優(yōu)先級占卧,那么我們在UI線程中創(chuàng)建了線程遗菠,則線程優(yōu)先級是和UI線程優(yōu)先級一樣,平等的和UI搶占CPU時間片資源华蜒。
  • JDK Api辙纬,限制了新設置的線程的優(yōu)先級必須為[1~10],優(yōu)先級priority的值越高,獲取CPU時間片的概率越高叭喜。UI線程優(yōu)先級為5
java.lang.Thread.setPriority(int newPriority)
  • Android Api贺拣,可以為線程設置更為精細的優(yōu)先級(-20~19),優(yōu)先級priority的值越低捂蕴,獲取CPU時間片的概率越高譬涡。UI線程優(yōu)先級為-10
android.os.Process.setThreadPriority(int newPriority)

線程的幾種狀態(tài)與常用方法

方法名 說明
NEW 初始狀態(tài),線程被新建啥辨,還沒調(diào)用start方法
RUNNABLE 運行狀態(tài)涡匀,把“運行中”和“就緒”統(tǒng)稱為運行狀態(tài)
BLOCKED 阻塞狀態(tài),表示線程阻塞于鎖
WAITING 等待狀態(tài)溉知,需要其他線程通知喚醒
TIME_WAITING 超時等待狀態(tài)陨瘩,表示可以在指定的時間超時后自行返回
TERMINATED 終止狀態(tài),表示當前線程已經(jīng)執(zhí)行完畢
image-20230723210346165.png
方法名 說明
wait 進入等待池级乍,釋放資源對象鎖拾酝,可使用notify.notifyAll,或等待超時來喚醒
join 等待目標線程執(zhí)行完成后再執(zhí)行此線程
yield 暫停當前正在執(zhí)行的線程對象,不會釋放資源鎖卡者,使同優(yōu)先級或更高優(yōu)先級的線程有執(zhí)行的機會
sleep 使調(diào)用線程進入休眠狀態(tài)腰湾,但在一個synchronized塊中執(zhí)行sleep脐湾,線程雖然會休眠果复,但不會釋放資源資源對象鎖

線程間消息通訊

  • 主線程向子線程發(fā)送消息
//自定義一個looper子線程
class LooperThread extends Thread {
    private Looper looper;

    public Looper getLooper(){
        synchronized (this){
            if(looper == null && isAlive()){
                try {
                    wait();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
        return looper;
    }

    @Override
    public void run() {
        Looper.prepare();
        synchronized (this){
            looper = Looper.myLooper();
            notifyAll();
        }
        Looper.loop();
    }
}

//主線程向子線程發(fā)送消息
LooperThread looperThread = new LooperThread("looper-thread");
looperThread.start();
Handler handler = new Handler(looperThread.getLooper()){
  @Override
  public void handleMessage(@NonNull Message msg){
    super.handleMessage(msg);
    Log.e(TAG,"handleMessage:" + msg.what);
    Log.e(TAG,"handleMessage:" + Thread.currentThread.getName);
  }
};
handler.sendEmptyMessage(MSG_WHAT_1);
  

  • 子線程向主線程發(fā)送消息(很熟悉颤枪,這里就省略了)

線程安全

什么是線程并發(fā)安全

線程安全的本質(zhì)是能夠讓并發(fā)線程有序的運行(這個有序有可能是先來后到的排隊,有可能有人插隊恒傻,但不管怎么樣脸侥,同一時刻只能一個線程有權訪問同步資源),線程執(zhí)行的結(jié)果盈厘,能夠?qū)ζ渌€程可見睁枕。

線程安全的幾種分類

  • synchronized關鍵字
  • ReentrantLock鎖
  • AtomicInteger...原子類


    image.png
  • synchronized,ReentrantLock-鎖


    image-20230729222013984.png
  • 原子類-自旋


    image-20230729222203332.png
  • 鎖適合寫操作多的場景沸手,先加鎖可以保證寫操作時數(shù)據(jù)正確外遇。
  • 原子類適合讀操作多的場景,不加鎖的特點能夠使其讀操作的性能大幅提升

如何保證線程安全

  • AtomicInteger原子包裝類

    • AtomicInteger原子包裝類,CAS(Compare-And-Swap)實現(xiàn)無鎖數(shù)據(jù)更新契吉。自旋的設計能夠有效避免線程因阻塞-喚醒帶來的系統(tǒng)資源開銷跳仿。
    • 使用場景:多線程計數(shù),原子操作捐晶,并發(fā)數(shù)量小的場景菲语。
    //1妄辩、構建對象
    AtomicInteger atomicInteger = new AtomicInteger(1);
    //2.調(diào)用 Api
    atomicInteger.getAndIncrement();
    atomicInteger.getAndAdd(2);
    
    atomicInteger.getAndDecrement();
    atomicInteger.getAndAdd(-2);
    
  • volatile 可見性修飾

    volatile修飾的成員變量在每次被線程訪問時,都強迫從共享內(nèi)存重新讀取該成員的值山上,而且眼耀,當成員變量值發(fā)生變化時,強迫變化的值重新寫入共享內(nèi)存佩憾。

    不能解決非原子操作的線程安全畔塔。性能不及原子類高。

    volatile int count;
    public void increment(){
      //其他線程可見
      count = 5;
      //非原子操作鸯屿,其他線程不可見
      count = count + 1;
      count ++;
    
  • synchronized

    鎖java對象,鎖Class對象把敢,鎖代碼塊

    • 鎖方法(本質(zhì)是鎖java對象)寄摆,加在方法上,未獲取到對象鎖的其他線程都不可以訪問該方法修赞。
    synchronized void printThreadName(){
      
    }
    
    • 鎖Class對象婶恼,加在static方法上相當于給Class對象加鎖,哪怕是不同的java對象實例柏副,也需要排隊執(zhí)行勾邦。
    static synchronized void printThreadName(){
      
    }
    
    • 鎖代碼塊,未獲取到對象鎖的其他線程可以執(zhí)行同步塊之外的代碼
    void printThreadName(){
      String name = Thread.currentThread.getName();
      System.out.println("線程:" + name + "準備好了...");
      synchronized(this){
        
      }
        
    }
    

    synchronized的優(yōu)勢是什么呢割择?

    • 哪怕我們一個同步方法中出現(xiàn)了異常眷篇,那么JVM也能夠為我們自動釋放鎖,能主動從而規(guī)避死鎖荔泳,不需要開發(fā)者手動釋放鎖蕉饼。

    劣勢是什么呢?

    • 必須要等到獲取鎖對象的線程執(zhí)行完成玛歌,或者出現(xiàn)異常昧港,才能釋放掉。不能中途中途釋放鎖支子,不能中斷一個正在試圖獲得鎖的線程创肥。
    • 另外咱們也不知道多線程競爭鎖的時候,獲取鎖成功與否值朋,所以不夠靈活叹侄。
    • 每個鎖僅有單一的條件(某個對象),不能設定超時昨登。
  • ReentrantLock悲觀鎖圈膏,可重入鎖,公平鎖篙骡,非公平鎖

    • 基本用法
    ReentrantLock lock = new ReentrantLock();
    try{
      lock.lock
        ...
    }finally{
      lock.unLock()
    }
    
    void lock();//獲取不到會阻塞
    boolean tryLock();//嘗試獲取鎖稽坤,成功返回true
    boolean tryLock(3000,TimeUnit.MILLISECONDS);//在一定時間內(nèi)去不斷嘗試獲取鎖
    void lockInterruptibly();//可使用Thread.interrupt()打斷阻塞狀態(tài)丈甸,退出競爭,讓給其他線程
    
    • 可重入尿褪,避免死鎖
    ReentrantLock lock = new ReentrantLock();
    public void doWork(){
      try{
        lock.lock();
        doWork();//遞歸調(diào)用睦擂,使得統(tǒng)一線程多次獲得鎖
      }finally{
        lock.unLock();
      }
    }
    
    • 公平鎖與非公平鎖

      • 公平鎖,所有進入阻塞的線程排隊依次均有機會執(zhí)行
      • 默認非公平鎖杖玲,允許線程插隊顿仇,避免每一個線程都進入阻塞,在喚醒摆马,性能高臼闻。因為線程可以插隊,導致隊列中可能會存在線程餓死的情況囤采,一直得不到鎖述呐,一直得不到執(zhí)行。
    • ReentrantLock進階用法——Condition條件對象

      可使用它的await-singnal指定喚醒一個(組)線程蕉毯。相比于wait-notify要么全部喚醒乓搬,要么只能喚醒一個,更加靈活可控

      ReentrantLock lock = new ReentrantLock();
      Condition worker1 = lock.newCondition();
      Condition worker2 = lock.newCondition();
      class Worker1{
        ...
          worker1.await();//進入阻塞代虾,等待喚醒
        ...  
      }
      
      class Worker2{
        ...
          worker2.await();//進入阻塞进肯,等待喚醒
      }
      
      class Boss{
        if(...){
          worker1.signal();//指定喚醒線程1
        }else{
          worker2.signal();//指定喚醒線程2
        }
      }
      
    • ReentrantReadWriteLock共享鎖,排他鎖

      • 共享鎖棉磨,所有線程均可同時獲得江掩,并發(fā)量高,比如在線文檔查看乘瓤。
      • 排他鎖频敛,同一時刻只有一個線程有權修改資源,比如在線文檔編輯馅扣。
    ReentrantReadWriteLock reentrantReadWriteLock;
    ReentrantReadWriteLock.ReadLock readLock;
    ReentrantReadWriteLock.WriteLock writeLock;
    

    如何正確的使用鎖&原子類

    • 減少持鎖時間

      盡管鎖在同一時間只能允許一個線程持有斟赚,其他想要占用鎖的線程都在臨界區(qū)外等待鎖的釋放,這個等待的時間我們希望盡可能的短差油。

      public void syncMethod(){
        noneLockedCode1();//2s
        synchronized(this){
          needLockedMethed();//2s
        }
        noneLockedCode2();//2s
      }
      
    • 鎖分離

      讀讀拗军,讀寫,寫讀蓄喇。只要有寫鎖進入才需要做同步處理发侵,但是對于大多數(shù)應用來說,讀的場景要遠大于寫的場景妆偏,因此一旦使用讀寫鎖刃鳄,在讀多寫少的場景中,就可以很好的提高系統(tǒng)的性能钱骂。

      讀鎖 寫鎖
      讀鎖 可以訪問 不可訪問
      寫鎖 不可訪問 不可訪問
    • 鎖粗化

      多次加鎖叔锐,釋放鎖合并成一次

      public void doSomethingMethod(){
        synchronized(lock){
          //do some thing
        }
        ...
          //這里還有一些代碼挪鹏,做其它不需要同步的工作,但能很快執(zhí)行完畢
        ...
        synchronized(lock){
          //do other thing
        }
      }
      
      public void doSomethingMethod(){
        //進行鎖粗化:整合成一次鎖請求愉烙、釋放
        synchronized(lock){
          //do something
          //做其它不需要同步的工作讨盒,但能很快執(zhí)行完畢
          //do other thing
        }
      }
      
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市步责,隨后出現(xiàn)的幾起案子返顺,更是在濱河造成了極大的恐慌,老刑警劉巖蔓肯,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遂鹊,死亡現(xiàn)場離奇詭異,居然都是意外死亡蔗包,警方通過查閱死者的電腦和手機秉扑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來气忠,“玉大人,你說我怎么就攤上這事赋咽【稍耄” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵脓匿,是天一觀的道長淘钟。 經(jīng)常有香客問我,道長陪毡,這世上最難降的妖魔是什么米母? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮毡琉,結(jié)果婚禮上铁瞒,老公的妹妹穿的比我還像新娘。我一直安慰自己桅滋,他們只是感情好慧耍,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著丐谋,像睡著了一般芍碧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上号俐,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天泌豆,我揣著相機與錄音,去河邊找鬼吏饿。 笑死踪危,一個胖子當著我的面吹牛蔬浙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播陨倡,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼敛滋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了兴革?” 一聲冷哼從身側(cè)響起绎晃,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎杂曲,沒想到半個月后庶艾,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡擎勘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年咱揍,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片棚饵。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡煤裙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出噪漾,到底是詐尸還是另有隱情硼砰,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布欣硼,位于F島的核電站题翰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏诈胜。R本人自食惡果不足惜豹障,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望焦匈。 院中可真熱鬧血公,春花似錦、人聲如沸缓熟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荚虚。三九已至薛夜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間版述,已是汗流浹背梯澜。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人晚伙。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓吮龄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親咆疗。 傳聞我的和親對象是個殘疾皇子漓帚,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容