作者: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)場,定位到了類似這樣的一個修改:
啊?只是設(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是什么:
原來真兇就是他!
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)什么情況:
來了,如果第二個參數(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)慎。
如果這篇文章對你有幫助,歡迎分享,收藏,點贊!






