亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

作者:leafjia,騰訊WXG客戶端開發(fā)工程師

你真的了解Android的線程優(yōu)先級嗎? 看似平平無奇的三行代碼卻隱藏著巨大的陷阱!

Android上如果在主線程執(zhí)行下面的代碼:

Thread t = new Thread();
t.start();
t.setPriority(3);

我們的預(yù)期應(yīng)該是子線程t的優(yōu)先級被設(shè)置為了低優(yōu)先級。

但真正運行后,我們驚奇的發(fā)現(xiàn),不只是子線程t,主線程的優(yōu)先級同樣會被設(shè)置為低優(yōu)先級!事實上,這三行代碼甚至導(dǎo)致了Android微信客戶端的一次線上故障!這是為什么?背后有怎樣秘密?又如何管控和避免?我們來一起深入分析、研究下這個問題。

(傳送門:如果不想深入了解這其中的原理,和一波三折的故事,可以直接跳到最后的 5.3 小節(jié),那里提供了一些設(shè)置線程優(yōu)先級的正確和錯誤的典型例子)

一、案件發(fā)生

微信Android客戶端某個新版本發(fā)布之后,馬上就收到了很多用戶的反饋:公眾號里的視頻卡頓/音畫不同步;朋友圈里的視頻卡頓掉幀。看現(xiàn)象,初步可能會懷疑到是不是播放器有什么問題?甚至懷疑是不是CDN有什么問題?然而事情遠沒有想象的那么簡單,在測試同學(xué)的幫助下,可以確定是Matrix的版本升級引起的。離譜!Matrix作為性能監(jiān)控的工具,居然會影響到視頻的播放?

 

二、案發(fā)現(xiàn)場

瀏覽器的同事先有了發(fā)現(xiàn):音視頻不同步,是因為pthread_cond_timedwait方法比設(shè)置的時間要長40ms左右導(dǎo)致。再經(jīng)過測試,好家伙!不止是這個方法,新版本微信里,所有的sleep/wait方法,都要比原本設(shè)置的時間多出了40ms左右。可以用下面的代碼,非常容易的復(fù)現(xiàn):

long startTime = System.currentTimeMillis();
Thread.sleep(10);
Log.i("Matrix","duration = " + (System.currentTimeMillis() - startTime));
//結(jié)果為:duration = 50

一開始我們完全沒有頭緒,根本不清楚到底是哪里的代碼引起的,好吧,最蠢的辦法,在新舊兩個Matrix版本的commit中,不斷二分,找到第一次出現(xiàn)問題的commit。終于找到了案發(fā)現(xiàn)場,定位到了類似這樣的一個修改:

你真的了解Android線程優(yōu)先級嗎?微信卡頓慘案分析

 

啊?只是設(shè)置線程優(yōu)先級與啟動線程的順序調(diào)換,況且設(shè)置的也只是一個特定子線程的優(yōu)先級,居然會有這么大的破壞力?沒錯,讀者有興趣可以在Android上運行這樣一段代碼:

Thread t = new Thread();
t.start();
t.setPriority(3);


long startTime = System.currentTimeMillis();
Thread.sleep(10);
Log.i("Matrix","duration = " + (System.currentTimeMillis() - startTime));  
// 結(jié)果為duration = 50ms

非常離奇的現(xiàn)象就是:如果在Thread的start后馬上調(diào)用setPriority,并且設(shè)置一個小于默認(rèn)優(yōu)先級(默認(rèn)優(yōu)先級為5)的優(yōu)先級,就會出現(xiàn)sleep/wait等方法會比設(shè)置的時間多幾十毫秒的現(xiàn)象。

而由于很多視頻播放的邏輯中,都會通過系統(tǒng)的sleep/wait/pthread_cond_timedwait等方法來實現(xiàn)音視頻的同步,會有非常頻繁的調(diào)用,如果每次都多出幾十毫秒,就會直接引起視頻播放卡頓,音畫不同步等問題,也就直接導(dǎo)致了這次微信的線上故障。

三、分析推理

離譜!只是想設(shè)置一個特定子線程的優(yōu)先級,居然就直接影響了主線程主線程創(chuàng)建的所有子線程的sleep時間?這里絕對隱藏著什么秘密。

(在一起來探秘之前,需要補充一個小的背景(如果你已經(jīng)充分了解linux線程的nice值,可以直接跳過):不管是在JAVA層設(shè)置線程優(yōu)先級,還是在Native層設(shè)置線程優(yōu)先級,最終設(shè)置的,也是絕大部分情況下最終起到作用的,都是線程的nice值,nice值越高,也就說明這個線程的“脾氣”越好,就越不容易搶到CPU,也就意味著線程的優(yōu)先級越低。)

3.1. 罪魁禍?zhǔn)祝篢imerSlackHigh

既然Thread的start和setPriority的順序改變會影響sleep/wait等方法的時間,我們就先看下setPriority做了什么事情,Thread.setPriority最終會調(diào)用到system/libartpalette/palette_android.cc里的PaletteSchedSetPriority方法:

palette_status_t PaletteSchedSetPriority(int32_t tid, int32_t managed_priority) {
    if (managed_priority < art::palette::kMinManagedThreadPriority ||
        managed_priority > art::palette::kMaxManagedThreadPriority) {
        return PALETTE_STATUS_INVALID_ARGUMENT;
    }
    int new_nice = kNiceValues[managed_priority - art::palette::kMinManagedThreadPriority]; 
    int curr_nice = getpriority(PRIO_PROCESS, tid);


    if (curr_nice == new_nice) {
        return PALETTE_STATUS_OK;
    }


    if (new_nice >= ANDROID_PRIORITY_BACKGROUND) {
        SetTaskProfiles(tid, {"SCHED_SP_BACKGROUND"}, true);
    } else if (curr_nice >= ANDROID_PRIORITY_BACKGROUND) {
        SchedPolicy policy;
        // Change to the sched policy group of the process.
        if (get_sched_policy(getpid(), &policy) != 0) {
            policy = SP_FOREGROUND;
        }
        SetTaskProfiles(tid, {get_sched_policy_profile_name(policy)}, true);
    }


    if (setpriority(PRIO_PROCESS, tid, new_nice) != 0) {
        return PALETTE_STATUS_CHECK_ERRNO;
    }
    return PALETTE_STATUS_OK;
}


static const int kNiceValues[art::palette::kNumManagedThreadPriorities] = {
    ANDROID_PRIORITY_LOWEST, // 1 (MIN_PRIORITY)
    ANDROID_PRIORITY_BACKGROUND + 6,
    ANDROID_PRIORITY_BACKGROUND + 3,
    ANDROID_PRIORITY_BACKGROUND,
    ANDROID_PRIORITY_NORMAL, // 5 (NORM_PRIORITY)
    ANDROID_PRIORITY_NORMAL - 2,
    ANDROID_PRIORITY_NORMAL - 4,
    ANDROID_PRIORITY_URGENT_DISPLAY + 3,
    ANDROID_PRIORITY_URGENT_DISPLAY + 2,
    ANDROID_PRIORITY_URGENT_DISPLAY // 10 (MAX_PRIORITY)
};

該方法會根據(jù)kNiceValues數(shù)組,把Java層Thread的優(yōu)先級映射為Linux的線程nice值,上面的例子中Java層priority是3,kMinManagedThreadPriority的值是1,所以經(jīng)過映射后,得到的nice值為13,而ANDROID_PRIORITY_BACKGROUND是10,表示后臺優(yōu)先級,原來我們設(shè)置了比后臺優(yōu)先級的nice值更高的值(即比后臺優(yōu)先級更低),此時系統(tǒng)會把該線程設(shè)置為后臺線程,具體做了什么呢?接著看SetTaskProfiles

bool SetTaskProfiles(int tid, const std::vector<std::string>& profiles, bool use_fd_cache) {
    return TaskProfiles::GetInstance().SetTaskProfiles(tid, profiles, use_fd_cache);
}


TaskProfiles::TaskProfiles() {
    // load system task profiles
    if (!Load(CgroupMap::GetInstance(), TASK_PROFILE_DB_FILE)) {
        LOG(ERROR) << "Loading " << TASK_PROFILE_DB_FILE << " for [" << getpid() << "] failed";
    }
    //省略
...... 
} 


static constexpr const char* TASK_PROFILE_DB_FILE = "/etc/task_profiles.json";

這里會去讀取/etc/task_profiles.json這個配置文件,我們在這個配置文件里搜索這里傳入的參數(shù)SCHED_SP_BACKGROUND,找到了這樣的配置:

{
    "Name": "SCHED_SP_BACKGROUND",
    "Profiles": [ "HighEnergySaving", "LowIoPriority", "TimerSlackHigh" ]
}
    {
      "Name": "TimerSlackHigh",
      "Actions": [
        {
          "Name": "SetTimerSlack",
          "Params":
          {
            "Slack": "40000000"
          }
        }
      ]
    },
    {
      "Name": "TimerSlackNormal",
      "Actions": [
        {
          "Name": "SetTimerSlack",
          "Params":
          {
            "Slack": "50000"
          }
        }
      ]
    }

被設(shè)置為后臺線程后,會設(shè)置三個profile:"HighEnergySaving", "LowIoPriority", "TimerSlackHigh"。

聰明的你應(yīng)該敏銳的發(fā)現(xiàn)了這個TimerSlackHigh,“懶惰定時器”,名字看起來就非常可疑,很有可能跟我們在查的sleep/wait延遲有關(guān),趕快查一下TimerSlack是什么:

你真的了解Android線程優(yōu)先級嗎?微信卡頓慘案分析

 

原來真兇就是他!

TimerSlack是Linux系統(tǒng)為了降低系統(tǒng)功耗,避免timer時間參差不齊,過于的頻繁的喚醒cpu,而設(shè)置的一種對齊策略。

總而言之:如果系統(tǒng)設(shè)置了大于等于10的nice值,即設(shè)置了比后臺優(yōu)先級還要低的優(yōu)先級,即把線程設(shè)置成了后臺線程,那么系統(tǒng)就會設(shè)置一個比較高的TimerSlack,從默認(rèn)的50微秒,提高到40毫秒,從而導(dǎo)致wait/sleep等掛起的時間多了40ms左右。

但是,還是不對勁啊,我設(shè)置的明明只是特定子線程的優(yōu)先級,按道理說只會影響該子線程的TimerSlack才對啊,為什么看起來影響了所有的線程呢?確實,這個問題還有很多疑點,我們接著分析。

3.2. 大誤會:糊涂的setPriority

我們首先就會懷疑Java層調(diào)用setPriority,設(shè)置錯了線程,那么找到 art/runtime/thread.cc

void Thread::SetNativePriority(int new_priority) {
    palette_status_t status = PaletteSchedSetPriority(GetTid(), new_priority);
    CHECK(status == PALETTE_STATUS_OK || status == PALETTE_STATUS_CHECK_ERRNO);
}

在這里之后才會調(diào)用之前提到的PaletteSchedSetPriority方法,我們發(fā)現(xiàn)這里傳遞的參數(shù)也就是設(shè)置優(yōu)先級的對象線程是GetTid(),繼續(xù)找到GetTid()

pid_t GetTid() const {
    return tls32_.tid;
}

tls32_是用來描述native線程的數(shù)據(jù)結(jié)構(gòu),繼續(xù)找下tls32_.tid是在哪里賦值的,art/runtime/thread.cc

void Thread::InitTid() {
    tls32_.tid = ::art::GetTid();
}


bool Thread::Init(ThreadList* thread_list, JavaVMExt* java_vm, JNIEnvExt* jni_env_ext) {
    //......
    InitTid();
    //......
}


void* Thread::CreateCallback(void* arg) {
    //......
    CHECK(self->Init(runtime->GetThreadList(), runtime->GetJavaVM(), self->tlsPtr_.tmp_jni_env));
    //......
}


void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) {
    //......
    pthread_create_result = pthread_create(&new_pthread,
                                           &attr,
                                           Thread::CreateCallback,
                                           child_thread);
    //......
}

一層一層往上找,來到CreateNativeThread方法,原來是在線程創(chuàng)建的時候在pthread_create的回調(diào)方法CreateCallback里面調(diào)用的InitTid,來設(shè)置的。而CreateNativeThread是Thread.start調(diào)用來創(chuàng)建native線程的地方。

對!你可能已經(jīng)想到了,這里是有時序問題的,pthread_create的回調(diào)CreateCallback是異步執(zhí)行的,所以start執(zhí)行完畢,并不能確保setNativePriority的tid參數(shù)已經(jīng)賦值,而一旦tid還未被賦值,那么這個時候調(diào)用setPriority,tid就是默認(rèn)值0

我們再看如果Native的系統(tǒng)調(diào)用setpriority的參數(shù)如果是0的話,會出現(xiàn)什么情況:

你真的了解Android線程優(yōu)先級嗎?微信卡頓慘案分析

 

來了,如果第二個參數(shù)tid是0的話,就會設(shè)置調(diào)用線程的優(yōu)先級!

那TimerSlack呢?在tid為0的情況下的表現(xiàn)是什么樣呢?我們找到最終設(shè)置TimerSlack的地方,system/core/libprocessgroup/task_profiles.cpp

bool SetTimerSlackAction::ExecuteForTask(int tid) const {
    //......
    if (tid == 0 || tid == GetThreadId()) {
        if (prctl(PR_SET_TIMERSLACK, slack_) == -1) {
            PLOG(ERROR) << "set_timerslack_ns prctl failed";
        }
    }


    return true;
}

TimerSlack最終是通過系統(tǒng)調(diào)用prctl方法設(shè)置的,如果tid是0,同樣會設(shè)置當(dāng)前線程的TimerSlack。

所以結(jié)論是,如果GetTid為0,那么無論是nice值還是TimerSlack都會對當(dāng)前的調(diào)用線程設(shè)置。

居然有這么詭異的陷阱!我們再回頭看引起問題的代碼,似乎就說得過去了:

 Thread t = new Thread();
 t.start();
 t.setPriority(3);


 long startTime = System.currentTimeMillis();
 Thread.sleep(10);
 Log.i("Matrix","duration = " + (System.currentTimeMillis() - startTime));

t.start()調(diào)用之后,在native的tid還沒有被設(shè)置好的時候,就執(zhí)行了下面的t.setPriority,這時GetTid返回值是0,native的setpriority的tid參數(shù)為0,就會把調(diào)用線程(Calling Thread)即主線程的nice設(shè)置為13,同時TimerSlack也被設(shè)置為40ms。有這樣的結(jié)果就說得通了。

然而,結(jié)束了嗎?因為時序問題,主線程被設(shè)置了高nice和高TimerSlack,理所當(dāng)然的,主線程創(chuàng)建的子線程的nice值和TimerSlack理所當(dāng)然地繼承了父線程,也就是主線程,所以自然也被影響了,是這樣嗎?

用這樣一段代碼測試:

Thread t = new Thread();
t.start();
t.setPriority(3);


long startTime = System.currentTimeMillis();
Thread.sleep(10);
Log.i("Matrix","MainThread duration = " + (System.currentTimeMillis() - startTime));  
//結(jié)果為50


new Thread(new Runnable() {
    @Override
    public void run() {
        long startTime = System.currentTimeMillis();
        Thread.sleep(10);
        Log.i("Matrix","Thread duration = " + (System.currentTimeMillis() - startTime));  
        //結(jié)果為10
    }
}).start();

我們發(fā)現(xiàn),主線程被錯誤設(shè)置nice值和TimerSlack后,創(chuàng)建的子線程并沒有繼承主線程的nice值和TimerSlack,這又是為什么呢?我們繼續(xù)分析。

在native層的線程,的確子線程會繼承父線程也就是主線程的nice值和TimerSlack,但是,start和priority的時序問題,只會錯誤地設(shè)置主線程的native的nice值和TimerSlack,并不會影響主線程Java層的priority變量,而子線程同樣會在Java層繼承父線程Java層的priority,而在native層創(chuàng)建線程的時候,有這樣的邏輯:

void* Thread::CreateCallback(void* arg) {
    //......
    ArtField* priorityField =         jni::DecodeArtField(WellKnownClasses::java_lang_Thread_priority);
    self->SetNativePriority(priorityField->GetInt(self->tlsPtr_.opeer));
    //......
}

在native創(chuàng)建線程調(diào)用到CreateCallback時,會根據(jù)Java層的priority,重新設(shè)置native層的priority/nice值和TimerSlack,而在上面的情況中,主線程的Java層priority并沒有被設(shè)置,是默認(rèn)的5,對應(yīng)的nice值為0,所以子線程的nice值就被再次設(shè)置為了0,也就重新被設(shè)置為了前臺線程,TimerSlack就又被設(shè)置為了低TimerSlack。

那么,問題又來了,既然主線程因為時序問題被錯誤地設(shè)置后臺優(yōu)先級后,并不影響其創(chuàng)建的子線程的nice值和TimerSlack,而線上故障中,引起音畫不同步和視頻掉幀的線程,卻又都是在主線程創(chuàng)建的子線程中產(chǎn)生的。那又是什么讓本來只會影響主線程的問題,進一步污染了它創(chuàng)建的全部子線程呢?

3.3. 幫兇:WebView推波助瀾

再經(jīng)過進一步排查,發(fā)現(xiàn)一個非常奇怪的現(xiàn)象,來看下面的代碼:

Thread t = new Thread();
t.start();
t.setPriority(3);


long startTime = System.currentTimeMillis();
Thread.sleep(10);
Log.i("Matrix","MainThread duration = " + (System.currentTimeMillis() - startTime));  
//結(jié)果為50


new WebView(context);


new Thread(new Runnable() {
    @Override
    public void run() {
        long startTime = System.currentTimeMillis();
        Thread.sleep(10);
        Log.i("Matrix","Thread duration = " + (System.currentTimeMillis() - startTime));  
        //結(jié)果為50
    }
}).start();

考驗眼力的時候來了,發(fā)現(xiàn)了跟之前的測試代碼有什么區(qū)別了嗎?是的,只要在創(chuàng)建子線程前加一句new WebView(context),那么子線程的nice和TimerSlack就也被影響了!

事實上,微信在某些頁面的確會有預(yù)加載webview的邏輯,我們也會發(fā)現(xiàn),也的確在new了WebView之后,設(shè)置優(yōu)先級的時序錯誤,就不止會影響主線程,而是其創(chuàng)建的所有的子線線程就都會被污染,都有了高TimerSlack,這是為什么呢?

我們在new Webview前后打印一下主線程nice值就會發(fā)現(xiàn),主線程nice值在執(zhí)行new WebView之前是13,之后變成了-4。

哦?new WebView居然會設(shè)置主線程的優(yōu)先級?

找到Chromium的源碼content/browser/browser_main_loop.cc

// Up the priority of the UI thread unless it was already high (since mac
// and recent versions of Android (O+) do this automatically).
if (base::FeatureList::IsEnabled(
    features::kBrowserUseDisplayThreadPriority) &&
    base::PlatformThread::GetCurrentThreadPriority() <
          base::ThreadPriority::DISPLAY) {
    base::PlatformThread::SetCurrentThreadPriority(
        base::ThreadPriority::DISPLAY);
}

其中ThreadPriority::DISPLAY的值就是-4,是的,Chromium有這樣的邏輯:如果當(dāng)前主線程的nice值大于-4(即優(yōu)先級較低),就會提高主線程的優(yōu)先級,并且這里設(shè)置優(yōu)先級是直接調(diào)用了native的setpriority,所以并不會同時修改TimerSlack

這個操作其實也很容易理解,是Chromium的保護措施,它不希望渲染UI的主線程處在一個較低的優(yōu)先級,不過卻陰差陽錯地成為了幫兇。

我們仔細看下,native線程在創(chuàng)建的時候根據(jù)Java層的priority設(shè)置nice值的時候的邏輯:

if (new_nice >= ANDROID_PRIORITY_BACKGROUND) {
    SetTaskProfiles(tid, {"SCHED_SP_BACKGROUND"}, true);
} else if (curr_nice >= ANDROID_PRIORITY_BACKGROUND) {
    SchedPolicy policy;
    // Change to the sched policy group of the process.
    if (get_sched_policy(getpid(), &policy) != 0) {
        policy = SP_FOREGROUND;
    }
    SetTaskProfiles(tid, {get_sched_policy_profile_name(policy)}, true);
}

如果新設(shè)置的nice值大于等于10,直接判斷為后臺線程,設(shè)置高的TimerSlack;但另一方面,只有當(dāng)前的nice值大于10,新的nice值小于10,才會把線程設(shè)置為前臺線程,設(shè)置低的TimerSlack

而執(zhí)行new WebView之后,主線程nice值是-4,而子線程繼承了主線程的nice值也是-4,-4 < 10,系統(tǒng)認(rèn)為你已經(jīng)是前臺線程了,就不會走到else if的邏輯,也就不會重新把線程設(shè)置為前臺線程,也就不會設(shè)置低TimerSlack,而子線程從主線程繼承的TimerSlack就是高TimerSlack。從而,主線程創(chuàng)建的全部子線程就都被污染了,也就引起了,音畫不同步和掉幀的故障。

至此才真正破案了,所有的疑問和“奇怪”現(xiàn)象也都可以解釋通了。

該問題一開始是由Thread的start和setPriority的時序問題,導(dǎo)致主線程被設(shè)置為后臺線程,同時被設(shè)置了高TimerSlack;而幫兇Chromium又在初始化WebView的時候默默地把主線程的nice值設(shè)置成了較低的nice值(較高的優(yōu)先級),但又沒有設(shè)置回低TimerSlack,從而主線程創(chuàng)建的子線程繼承了主線程的nice值和高TimerSlack后,卻認(rèn)為自己已經(jīng)是前臺線程,所以也沒有機會根據(jù)Java層的priority重新設(shè)置低TimerSlack,最后就導(dǎo)致了主線程連同其創(chuàng)建的所有子線程的TimerSlack全部都被設(shè)置為了高TimerSlack,從而產(chǎn)生了一系列的問題,導(dǎo)致了這次故障。

四、監(jiān)控機制:

原理已經(jīng)搞清楚后,我們需要建立一個監(jiān)控機制,避免之后再出現(xiàn)這種情況。

首先我們需要確保住主線程優(yōu)先級不被設(shè)置的過低,hook系統(tǒng)調(diào)用setpriority,如果對主線程設(shè)置過低的優(yōu)先級(過高的nice值),則直接報錯:

int (*original_setpriority)(int __which, id_t __who, int __priority);


int my_setpriority(int __which, id_t __who, int __priority) {
    
    if (__priority <= 0) {
        return original_setpriority(__which, __who, __priority);
    }
    
    if (__who == 0 && getpid() == gettid()) {
        // Throw Crash Here
    } else if (__who == getpid()) {
        // Throw Crash Here
    }


    return original_setpriority(__which, __who, __priority);
}

另外,我們還hook了設(shè)置TimerSlack的prctl方法,確保主線程的TimerSlack值不被設(shè)置的過大:

int (*original_prctl)(int option, unsigned long arg2, unsigned long arg3,
              unsigned long arg4, unsigned long arg5);


int my_prctl(int option, unsigned long arg2, unsigned long arg3,
             unsigned long arg4, unsigned long arg5) {
    if(option == PR_SET_TIMERSLACK) {
        if (gettid()==getpid() && arg2 > 50000) {
            // Throw Crash Here
        }
    }
    return original_prctl(option, arg2, arg3, arg4, arg5);
}

監(jiān)控會在微信各種體驗版和debug的時候打開,確保當(dāng)再次出現(xiàn)主線程的優(yōu)先級或者TimerSlack被錯誤設(shè)置的情況時,能夠提前發(fā)現(xiàn),避免這類問題被帶到線上。

五、額外的結(jié)論:

最后,我們再討論下,在設(shè)置優(yōu)先級的時候我們?nèi)菀壮霈F(xiàn)的一些錯誤。

5.1. 線程優(yōu)先級的“雙標(biāo)”

Thread在Java層的優(yōu)先級與Native層或者說Linux系統(tǒng)層的線程優(yōu)先級,也就是nice值,是兩套不同的標(biāo)準(zhǔn),數(shù)字大小的意義甚至也是相反的,容易產(chǎn)生混淆和誤用。

通過Thread.setPriority方法設(shè)置的優(yōu)先級是在Java層的優(yōu)先級,數(shù)字從0到10,數(shù)字越大表優(yōu)先級越高,默認(rèn)是5,Android主線程默認(rèn)是5。Java層的優(yōu)先級通過native層的kNiceValues數(shù)組,映射為nice值,再起到作用。

通過android.os.Process.setThreadPriority方法設(shè)置的優(yōu)先級是Native層的線程優(yōu)先級/nice值,數(shù)字從-20到20,數(shù)字越大代表優(yōu)先級越低,默認(rèn)是0,Android主線程默認(rèn)的nice值是-10。另外,native層的系統(tǒng)調(diào)用setpriority當(dāng)然也是直接設(shè)置的nice值。

5.2. 正確設(shè)置HandlerThread的優(yōu)先級

怎么設(shè)置HandlerThread的優(yōu)先級呢?第一反應(yīng)可能會這樣寫

HandlerThread ht = new HandlerThread("leafjia-thread");
ht.setPriority(3);
ht.start();

似乎也沒什么問題哦?但是這樣設(shè)置,其實除了設(shè)置了Java層Thread對象的成員變量priority,并不會起到任何其他的效果。

我們看HandlerThread的源碼:

public class HandlerThread extends Thread {
    int mPriority;
    //......
    
    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }


    //......


    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
    //......
}

調(diào)用ht.serPriority(3)其實設(shè)置的是其父類Thread的priority成員變量,而HandlerThread本身有自己的mPriority成員變量,start之后,會在創(chuàng)建Native Thread的時候,在調(diào)用run回調(diào)方法前,根據(jù)Java層Thread的priority(我們已經(jīng)設(shè)置為了3)設(shè)置Native的nice值,這時的確優(yōu)先級能夠設(shè)置成功。但是HandlerThread自己重寫了run方法,在之后執(zhí)行的run方法中,又再次通過Process.setThreadPriority(mPriority)設(shè)置了自己的優(yōu)先級為mPriority,而mPriority并不能通過Thread.setPriority方法設(shè)置。所以上面的代碼并不生效。

正確的方法也顯而易見:

HandlerThread ht = new HandlerThread("My-Thread", 13);
ht.start();

需要注意的是,因為線程優(yōu)先級最終是通過Process.setThreadPriority方法實現(xiàn)的,所以priority使用的是-20到20的nice值的優(yōu)先級體系。

5.3. Some Cases

進一步,可以總結(jié)出下面幾種設(shè)置線程優(yōu)先級的case,如果我們的目的是設(shè)置thread線程的優(yōu)先級為3(而不想改變調(diào)用線程的優(yōu)先級),即nice值為13,那么:

//Case1: 正確,thread線程的nice值被設(shè)置為13
Thread thread = new Thread();
thread.setPriority(3);
thread.start();


//Case2: 正確,thread線程的nice值被設(shè)置為13
Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        Thread.currentThread().setPriority(3);
        // 或:
        // Process.setThreadPriority(13);
    }
});
thread.start();


//Case3: 錯誤,thread線程的nice值一定被設(shè)置為13
//(可能直接被設(shè)置或從當(dāng)前線程繼承而來,視時序而定),
// 當(dāng)前線程的nice值很有可能被設(shè)置為13
Thread thread = new Thread();
thread.start();
thread.setPriority(3);


//Case4: 錯誤,同上
HandlerThread thread = new HandlerThread("My-Thread");
thread.start();
thread.setPriority(3);


//Case5: 錯誤,thread線程的nice值被設(shè)置為默認(rèn)的0
HandlerThread thread = new HandlerThread("My-Thread");
thread.setPriority(3);
thread.start();


//Case6: 正確,thread線程的nice值被設(shè)置為13
HandlerThread thread = new HandlerThread("My-Thread", 13);
thread.start();

至此,就是本文的全部內(nèi)容。線程優(yōu)先級的相關(guān)問題,大家平時可能關(guān)注的并不多,或者會認(rèn)為線程優(yōu)先級的影響并不會特別大。而看起來平平無奇的設(shè)置線程優(yōu)先級的三行代碼,就真的引起了一次線上故障。這也提醒大家,一旦涉及到多線程,就一定要對時序問題特別謹(jǐn)慎。

如果這篇文章對你有幫助,歡迎分享,收藏,點贊!

分享到:
標(biāo)簽:Android
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定