亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Android?線(xiàn)程死鎖場(chǎng)景與優(yōu)化解決

 更新時(shí)間:2023年12月29日 11:07:10   作者:時(shí)光少年  
線(xiàn)程死鎖是老生常談的問(wèn)題,線(xiàn)程池死鎖本質(zhì)上屬于線(xiàn)程死鎖的一部分,線(xiàn)程池造成的死鎖問(wèn)題往往和業(yè)務(wù)場(chǎng)景相關(guān),本文主要介紹了Android?線(xiàn)程死鎖場(chǎng)景與優(yōu)化,感興趣的可以了解一下

前言

線(xiàn)程死鎖是老生常談的問(wèn)題,線(xiàn)程池死鎖本質(zhì)上屬于線(xiàn)程死鎖的一部分,線(xiàn)程池造成的死鎖問(wèn)題往往和業(yè)務(wù)場(chǎng)景相關(guān),當(dāng)然更重要的是對(duì)線(xiàn)程池的理解不足,本文根據(jù)場(chǎng)景來(lái)說(shuō)明一下常見(jiàn)的線(xiàn)程池死鎖問(wèn)題,當(dāng)然也會(huì)包含線(xiàn)程死鎖問(wèn)題。

線(xiàn)程死鎖場(chǎng)景

死鎖的場(chǎng)景很多,有線(xiàn)程池相關(guān),也有與線(xiàn)程相關(guān),線(xiàn)程相關(guān)的線(xiàn)程池上往往也會(huì)出現(xiàn),反之卻不一定,本文會(huì)總結(jié)一些常見(jiàn)的場(chǎng)景,當(dāng)然有些場(chǎng)景后續(xù)可能還需要補(bǔ)充。

經(jīng)典互斥關(guān)系死鎖

這種死鎖是最常見(jiàn)的經(jīng)典死鎖,假定存在 A、B 2 個(gè)任務(wù),A 需要 B 的資源,B 需要 A 的資源,雙方都無(wú)法得到時(shí)便出現(xiàn)了死鎖,這種情況是鎖直接互相等待引發(fā),一般的情況下通過(guò)dumpheap 的lock hashcode就能發(fā)現(xiàn),相對(duì)來(lái)說(shuō)容易定位的多。

    //首先我們先定義兩個(gè)final的對(duì)象鎖.可以看做是共有的資源.
    final Object lockA = new Object();
    final Object lockB = new Object();
//生產(chǎn)者A

class  ProductThreadA implements Runnable{
    @Override
    public void run() {
//這里一定要讓線(xiàn)程睡一會(huì)兒來(lái)模擬處理數(shù)據(jù) ,要不然的話(huà)死鎖的現(xiàn)象不會(huì)那么的明顯.這里就是同步語(yǔ)句塊里面,首先獲得對(duì)象鎖lockA,然后執(zhí)行一些代碼,隨后我們需要對(duì)象鎖lockB去執(zhí)行另外一些代碼.
        synchronized (lockA){
            //這里一個(gè)log日志
            Log.e("CHAO","ThreadA lock  lockA");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB){
                //這里一個(gè)log日志
                Log.e("CHAO","ThreadA lock  lockB");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }
    }
}
//生產(chǎn)者B
class  ProductThreadB implements Runnable{
    //我們生產(chǎn)的順序真好好生產(chǎn)者A相反,我們首先需要對(duì)象鎖lockB,然后需要對(duì)象鎖lockA.
    @Override
    public void run() {
        synchronized (lockB){
            //這里一個(gè)log日志
            Log.e("CHAO","ThreadB lock  lockB");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockA){
                //這里一個(gè)log日志
                Log.e("CHAO","ThreadB lock  lockA");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }
    }
}
    //這里運(yùn)行線(xiàn)程
    ProductThreadA productThreadA = new ProductThreadA();
    ProductThreadB productThreadB = new ProductThreadB();

    Thread threadA = new Thread(productThreadA);
    Thread threadB = new Thread(productThreadB);
    threadA.start();
    threadB.start();

這類(lèi)問(wèn)題需要進(jìn)行排查和不斷的優(yōu)化,重點(diǎn)是優(yōu)化邏輯盡量減少鎖的使用,同時(shí)優(yōu)化調(diào)度機(jī)制。

Submit遞歸等待調(diào)用死鎖

原理是在固定的線(xiàn)程池?cái)?shù)量中,不斷的 submit 任務(wù),并且從工作線(xiàn)程通過(guò)get等待任務(wù)完成,

但是線(xiàn)程池?cái)?shù)量是固定的,從頭到尾所有的線(xiàn)程沒(méi)執(zhí)行完成,某次 submit 時(shí)就沒(méi)有足夠的線(xiàn)程來(lái)處理任務(wù),所有任務(wù)都處于等待。

ExecutorService pool = Executors.newSingleThreadExecutor(); //使用一個(gè)線(xiàn)程數(shù)模擬
pool.submit(() -> {
        try {
            log.info("First");
             //上一個(gè)線(xiàn)程沒(méi)有執(zhí)行完,線(xiàn)程池沒(méi)有線(xiàn)程來(lái)提交本次任務(wù),會(huì)處于等待狀態(tài)
            pool.submit(() -> log.info("Second")).get();
            log.info("Third");
        } catch (InterruptedException | ExecutionException e) {
           log.error("Error", e);
        }
   });

對(duì)于這種特殊邏輯,一定要思考清楚get方法調(diào)用的意義,如果僅僅為了串行執(zhí)行,使用一般隊(duì)列即可,當(dāng)然你也可以join其他線(xiàn)程。

公用線(xiàn)程池線(xiàn)程 size 不足造成的死鎖

該類(lèi)死鎖一般是把一個(gè)Size有限的線(xiàn)程池用于多個(gè)任務(wù)。

假定 A,B 兩個(gè)業(yè)務(wù)各需要2個(gè)線(xiàn)程處理生產(chǎn)者和消費(fèi)者業(yè)務(wù),且每個(gè)業(yè)務(wù)都有自己的lock,但是業(yè)務(wù)之間的lock沒(méi)有關(guān)聯(lián)關(guān)系。提供一個(gè)公共線(xiàn)程池,線(xiàn)程大小為2,顯然比較合理的執(zhí)行任務(wù)需要4個(gè),或者至少3個(gè),在線(xiàn)程數(shù)量不足的情況下這種情況下死鎖會(huì)高概率發(fā)生。

情形一:A,B 有序執(zhí)行,不會(huì)造成死鎖

情形二: A、B 并發(fā)執(zhí)行,造成死鎖

情形二出現(xiàn)的原因是 A,B 各分配了一個(gè)線(xiàn)程,當(dāng)他們執(zhí)行的條件都不滿(mǎn)足的時(shí)處于要wait狀態(tài),這時(shí)線(xiàn)程池沒(méi)有更多的線(xiàn)程提供,將導(dǎo)致 A、B 處于死鎖。

因此,對(duì)于公用線(xiàn)程池的使用,Size不要設(shè)置過(guò)低,同時(shí)要盡可能避免加鎖和太耗時(shí)的任務(wù),如果有加鎖和太耗時(shí)的需求,可以嘗試使用專(zhuān)用線(xiàn)程池。

RejectedExecutionHandler 使用不當(dāng)造成的 “死鎖”

嚴(yán)格意義上不能稱(chēng)為死鎖,但是這也是非常容易忽視的問(wèn)題。原因在沒(méi)檢測(cè)線(xiàn)程池狀態(tài)的情況下,通過(guò)RejectionExectutionHandler回調(diào)方法中將任務(wù)重新加回去,如此往復(fù)循環(huán),鎖住Caller線(xiàn)程。

一般處理任務(wù)時(shí),觸發(fā)該 RecjectedExecutionHandler 的情況分為 2 類(lèi),主要是 "線(xiàn)程池關(guān)閉"、“線(xiàn)程隊(duì)列和線(xiàn)程數(shù)已經(jīng)達(dá)到最大容量”,那么問(wèn)題一般出現(xiàn)在前者,如果線(xiàn)程池 shutdown 關(guān)閉之后,我們嘗試在該 Handler 中重新加入任務(wù)到線(xiàn)程池,那么會(huì)造成死循環(huán)問(wèn)題。

鎖住死循環(huán)

鎖住死循環(huán)本身也是一種死鎖,導(dǎo)致其他想獲取鎖資源的線(xiàn)程無(wú)法正常獲取中斷。

synchronized(lock){
  while(true){
   // do some slow things
  }
}

這種循環(huán)鎖也是相當(dāng)經(jīng)典,如果while內(nèi)部沒(méi)有wait的調(diào)用或者return或者break,那么這個(gè)鎖會(huì)一直存在。

文件鎖 & lock互斥

嚴(yán)格來(lái)說(shuō)這種相對(duì)復(fù)雜,有可能是文件鎖與lock互斥,也有可能是多進(jìn)程文件鎖獲取時(shí)阻塞之后無(wú)法釋放,導(dǎo)致java lock一直無(wú)法釋放,因此對(duì)于發(fā)生死鎖時(shí),dumpheap時(shí)不要忽略文件操作相關(guān)的堆棧。

可見(jiàn)性不足

通常情況下,這不是死鎖,而是線(xiàn)程無(wú)限循環(huán),以至于該線(xiàn)程無(wú)法被其他任務(wù)使用,我們對(duì)一些線(xiàn)程循環(huán)會(huì)加一個(gè)變量標(biāo)記其是否結(jié)束,但是如果可見(jiàn)性不足,也將無(wú)法造成退出的后果。
下面我們用主線(xiàn)程和普通線(xiàn)程模擬,我們?cè)谄胀ň€(xiàn)程中修改變量A,但是A變量在主線(xiàn)程中可見(jiàn)性不足,導(dǎo)致主線(xiàn)程阻塞。

public class ThreadWatcher {
    public int A = 0;
    public static void main(String[] args) {
        final ThreadWatcher threadWatcher = new ThreadWatcher();
        WorkThread t = new WorkThread(threadWatcher);
        t.start();
        while (true) {
            if (threadWatcher.A == 1) {
                System.out.println("Main Thread exit");
                break;
            }
        }
    }
}

class WorkThread extends Thread {
    private ThreadWatcher threadWatcher;
    public WorkThread(ThreadWatcher threadWatcher) {
        super();
        this.threadWatcher = threadWatcher;
    }
    @Override
    public void run() {
        super.run();
        System.out.println("sleep 1000");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.threadWatcher.A = 1;
        System.out.println("WorkThread exit");

    }
}

打印結(jié)果:

sleep 1000   
WorkThread exit

由于A缺乏可見(jiàn)性,導(dǎo)致主線(xiàn)程一直循環(huán),這里有必要加上volatile或者使用atomic類(lèi),或者使用synchronized進(jìn)行同步。注意,不能用final,final只能保證指令不可亂序,但不能保證可見(jiàn)性。

CountDownLatch 初始值過(guò)大

這個(gè)原因?qū)儆诰幊虇?wèn)題,比如需要2次countDown完成等待,而初始值為3次以上,必然導(dǎo)致等待的線(xiàn)程卡住。

CountDownLatch latch = new CountDownLatch(6);
ExecutorService service = Executors.newFixedThreadPool(5); 
for(int i=0;i< 5;i++){
    
final int no = i+1;
Runnable runnable=new Runnable(){
    @Override 
    public void run(){
            try{
                Thread.sleep((long)(Math.random()*10000));
                System.out.println("No."+no+"準(zhǔn)備好了。");
            }catch(InterruptedException e){
                e.printStackTrace();
            }finally{
                latch.countDown();
            }
    }
};
service.submit(runnable);
}
System.out.println("開(kāi)始執(zhí)行.....");
latch.await();
System.out.println("停止執(zhí)行");

實(shí)際上這種問(wèn)題排查起來(lái)比較容易,對(duì)于計(jì)數(shù)式waiter,一定確保waiter能結(jié)束,即使發(fā)生異常行為。

線(xiàn)程死鎖優(yōu)化建議

死鎖一般和阻塞有關(guān),對(duì)待死鎖問(wèn)題,不妨換一種方式。

常見(jiàn)的優(yōu)化方法

1、可以有序執(zhí)行,當(dāng)然這種也降低了并發(fā)優(yōu)勢(shì)
2、不要共用同一線(xiàn)程池,如果要共用,避免加鎖,阻塞和懸掛
3、使用公共鎖資源的 wait (long timeout) 機(jī)制,讓線(xiàn)程超時(shí)
4、如果過(guò)于擔(dān)心線(xiàn)程池不能回收,建議使用 keepaliveTime+allowCoreThreadTimeOut,回收線(xiàn)程但不影響線(xiàn)程狀態(tài),可以繼續(xù)提交任務(wù)。
5、必要時(shí)擴(kuò)大線(xiàn)程池大小

公用線(xiàn)程任務(wù)移除

如果公共線(xiàn)程池正在執(zhí)行的線(xiàn)程阻塞了,那所有的任務(wù)需要等待,對(duì)于不重要的任務(wù),可以選擇移除。

實(shí)際上正在執(zhí)行的線(xiàn)程任務(wù)很難去終止,公用線(xiàn)程池可能造成大量任務(wù)pending,但是從公用線(xiàn)程池中移除任務(wù)隊(duì)列顯然是比較危險(xiǎn)的操作。一種可行的方法是warp task,每次添加runnable時(shí)記錄這些Task,退出特定業(yè)務(wù)時(shí)清理Warpper中的target目標(biāo)任務(wù)

public class RemovableTask implements Runnable {
    private static final String TAG = "RemovableTask";
    private Runnable target  = null;
    private Object lock = new Object();

    public RemovableTask(Runnable task) {
        this.target = task;
    }

    public static RemovableTask warp(Runnable r) {
        return new RemovableTask(r);
    }

    @Override
    public void run() {
        Runnable task;
        synchronized (this.lock) {
            task = this.target;
        }
        if (task == null) {
            MLog.d(TAG,"-cancel task-");
            return;
        }
        task.run();
    }

    public void dontRunIfPending() {
        synchronized (this.lock) {
            this.target = null;
        }
    }
}

下面進(jìn)行任務(wù)清理

public void purgHotSongRunnable() {
    for (RemovableTask r : pendingTaskLists){
        r.dontRunIfPending();
    }
}

注意,這里仍然還可以利用享元模式優(yōu)化,減少RemovableTask的創(chuàng)建。

使用多路復(fù)用或協(xié)程

對(duì)于鎖比較厭惡的開(kāi)發(fā)者可以使用多路復(fù)用或協(xié)程,這種情況下存避免不必要的等待,將wait轉(zhuǎn)化為notify,減少上下文切換,可以提高線(xiàn)程的執(zhí)行效率。
說(shuō)到對(duì)協(xié)程觀(guān)點(diǎn),一直存在爭(zhēng)議:

(1)協(xié)程是輕量級(jí)線(xiàn)程?但從cpu和系統(tǒng)角度,協(xié)程和多路復(fù)用都不是輕量級(jí)線(xiàn)程,CPU壓根不認(rèn)識(shí)這貨,因此不可能比線(xiàn)程快,他只能加速線(xiàn)程的執(zhí)行,Okhttp也不是輕量級(jí)Socket,再快也快不過(guò)Socket,他們都是并發(fā)編程框架或者風(fēng)格。

(2)kotlin也不是假協(xié)程,有觀(guān)點(diǎn)說(shuō)kotlin會(huì)創(chuàng)建線(xiàn)程所以是假協(xié)程?epoll多路復(fù)用機(jī)制,難道所有任務(wù)都是epoll執(zhí)行的么?簡(jiǎn)單的例子,從磁盤(pán)拷貝文件到內(nèi)存,雖然CPU不參與,但DMA也是芯片,毫無(wú)疑問(wèn),也算線(xiàn)程。協(xié)程在用戶(hù)態(tài)執(zhí)行耗時(shí)任務(wù),如果不啟用線(xiàn)程,難不成要插入無(wú)數(shù)entry point 讓單個(gè)線(xiàn)程執(zhí)行一個(gè)任務(wù)?顯然,對(duì)于協(xié)程的認(rèn)知,有人夸有人貶,主要原因還是是對(duì)于“框架”和執(zhí)行單元存在認(rèn)知問(wèn)題。

降低鎖粒度

JIT對(duì)鎖的優(yōu)化分為鎖消除和鎖重入,但是很難對(duì)鎖粒度進(jìn)行優(yōu)化,因此,不要添加過(guò)大的代碼段顯然是必要的,因此有些耗時(shí)邏輯本身不涉及變量的修改,大可不必加鎖,只對(duì)修改變量的部分加鎖即可。

總結(jié)

本文主要是對(duì)死鎖的問(wèn)題的優(yōu)化建議,至于性能問(wèn)題,其實(shí)我們遵循一個(gè)原則:在保證流暢度的情況下線(xiàn)程越少越好。對(duì)于必要存在的線(xiàn)程,可以使用隊(duì)列緩沖、逃逸分析、對(duì)象標(biāo)量化、鎖消除、鎖粗化、降低鎖范圍、多路復(fù)用、消除同步屏障、協(xié)程的角度去優(yōu)化。

到此這篇關(guān)于Android 線(xiàn)程死鎖場(chǎng)景與優(yōu)化解決的文章就介紹到這了,更多相關(guān)Android 線(xiàn)程死鎖 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論