Android線程學(xué)習(xí)索引

目錄

  • 線程狀態(tài)
  • 線程池
  • 線程安全
  • Java Memory Model
  • Volatile
  • Sychornized
  • ReentrantLock
  • 樂觀悲觀
  • 死鎖
  • jmm和jvm的區(qū)別
  • 線程通信handler機(jī)制
    • IntentService
    • ThreadLocal
  • 原子類(沒寫)
  • AsyncTask
  • 其他的同步
  • 進(jìn)程
  • 參考鏈接

線程狀態(tài)

線程狀態(tài)轉(zhuǎn)換圖


new,runnable,running,dead,blocked,waiting/time waiting

狀態(tài)轉(zhuǎn)換的幾個方法及其比較

start()/run()的區(qū)別:

start是讓線程處于就緒狀態(tài)真正的實(shí)現(xiàn)多線程,內(nèi)部是調(diào)用了run方法。而run方法不是,繼續(xù)同步調(diào)用。

其余幾個方法的比較:

yield—線程靜態(tài)方法,釋放鎖,放棄這次cpu機(jī)會,下次再競爭,只給不比自己優(yōu)先級低的線程機(jī)會,執(zhí)行后線程進(jìn)入runnable狀態(tài)

sleep--靜態(tài)方法,不釋放鎖,不考慮優(yōu)先級,執(zhí)行后進(jìn)入block狀態(tài),有的也說進(jìn)入timewaiting狀態(tài),從不釋放鎖這點(diǎn)來看,我覺得是block狀態(tài)。

join--實(shí)例方法,使該實(shí)例進(jìn)程阻塞,當(dāng)被join的線程執(zhí)行完才執(zhí)行自己,執(zhí)行進(jìn)入wait/timewaiting狀態(tài)

wait—Object類實(shí)例方法,釋放鎖,必須放在同步塊里使用,自己則進(jìn)入對象等待池,執(zhí)行后進(jìn)入wait/timewaiting,wait(time)就是進(jìn)入timewaiting狀態(tài),能提前notify么?

notify--實(shí)例方法,該實(shí)例對象移除對象等待池,進(jìn)入鎖標(biāo)志等待池

notifyAll--實(shí)例方法,喚醒所有的線程,讓其競爭上崗


怎樣使用多線程

繼承thread,實(shí)現(xiàn)runnable接口,實(shí)現(xiàn)callable接口


線程池

線程池ThreadPool的使用executorService.excute,

根據(jù)其構(gòu)造方法理解線程池

比如asyncTask的核心線程數(shù)Math.max(2,Math.min(CPU_COUNT,4)),至少2個,至多4個
最大線程數(shù) CPU_COUNT*2-1

okhttp的核心線程數(shù)0,最大數(shù)INT_MAX---為什么會這樣?后文有

優(yōu)化點(diǎn)就在減少線程的創(chuàng)建和銷毀時間,開發(fā)者更多的關(guān)注任務(wù)

構(gòu)造方法的四個隊(duì)列及其特點(diǎn)

linkedBlockQueue,ArrayBlockQueue,DelayQueue,SynchronizedQueue

Okhttp的內(nèi)部實(shí)現(xiàn)

可以借助OkHttpClient.newCall(request).equeue(Callback)來理解線程池的應(yīng)用
參考o(jì)khttp章節(jié)的發(fā)送請求段落
http://www.itdecent.cn/p/6fa13048a6cf


線程安全問題

實(shí)質(zhì)就是多線程的同步問題。
為什么會出現(xiàn)同步問題,背景在數(shù)據(jù)讀寫這里,先看到讀寫方式(如圖)

image.png

1.程序運(yùn)行過程中的臨時數(shù)據(jù)是存放在主存(物理內(nèi)存)當(dāng)中的;
cpu訪問速度和順序:register寄存器-->cpu cache mem(高速緩存)-->RAM 主存
2.由于CPU執(zhí)行速度很快,而從內(nèi)存讀取數(shù)據(jù)和向內(nèi)存寫入數(shù)據(jù)的過程跟CPU執(zhí)行指令的速度比起來要慢的多;
3.如果任何時候?qū)?shù)據(jù)的操作都要通過和內(nèi)存的交互來進(jìn)行,會大大降低指令執(zhí)行的速度;
4.所以最終在CPU里面就有了高速緩存;
線程會從主存(堆區(qū))copy一份到棧內(nèi)存里(高速緩存里),處理完后就把這個副本刷回去
5.但是問題在,有緩存不一致的問題,也就是多線程訪問會訪問到緩存,而不是實(shí)時數(shù)據(jù);
6.于是有了加鎖的概念(線程安全的問題)。


Java Memory Model

解決線程安全模型,先來了解jmm

什么是Java內(nèi)存模型?

https://mp.weixin.qq.com/s/_4AtyCVPj6E4AkHzVHsKbg

http://www.cnblogs.com/dolphin0520/p/3920373.html

因此并發(fā)可能遇到的三個問題
1.原子性:即一個操作或者多個操作 要么全部執(zhí)行并且執(zhí)行的過程不會被任何因素打斷,要么就都不執(zhí)行;
2.可見性:是指當(dāng)多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值;
3.有序性:即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。

再更詳細(xì)的說
java內(nèi)存模型的毛病
http://cmsblogs.com/?p=2161

實(shí)例化一個對象要分為三個步驟:

1.分配內(nèi)存空間
2.初始化對象
3.將內(nèi)存空間的地址賦值給對應(yīng)的引用

但是由于重排序的緣故,步驟2、3可能會發(fā)生重排序,其過程如下:
1.分配內(nèi)存空間
2.將內(nèi)存空間的地址賦值給對應(yīng)的引用
3.初始化對象

如果2、3發(fā)生了重排序就會導(dǎo)致第二個判斷會出錯,singleton != null,但是它其實(shí)僅僅只是一個地址而已,
此時對象還沒有被初始化,所以return的singleton對象是一個沒有被初始化的對象

兩個解決辦法:

1.不允許初始化階段步驟2 、3發(fā)生重排序。
2.允許初始化階段步驟2 、3發(fā)生重排序,但是不允許其他線程“看到”這個重排序。


Volatile

volatile關(guān)鍵字,用來修飾變量
volatile在以下三方面影響java內(nèi)存模型,從而保證線程安全

1.原子性:Java中對基本數(shù)據(jù)類型的變量的讀取和賦值操作是原子性操作,即這些操作是不可被中斷的,要么執(zhí)行,要么不執(zhí)行,其余都是非原子操作;
*volatile不保證原子性操作,所以對原子性沒幫助

2.可見性:Java里的一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存,當(dāng)有其他線程需要讀取時,它會去內(nèi)存中讀取新值;
*volatile保證修改的值會立即被更新到主存

3.有序性:JavaJ內(nèi)存模型中,允許編譯器和處理器對指令進(jìn)行重排序,但是重排序過程不會影響到單線程程序的執(zhí)行,卻會影響到多線程并發(fā)執(zhí)行的正確性;
*volatile原則屬于 happens-before原則的一部分,能部分保證有序性

從匯編角度了解volatile禁止重排序,了解happens-before原則
http://cmsblogs.com/?p=2148


Synchronize

Synchronize關(guān)鍵字,本身不是鎖,只是去拿鎖

從jmm角度分析synchronize和volatile的區(qū)別
1.原子性
代表了原子性的操作(幾個步驟是一個原子)在線程執(zhí)行過程中不會被中斷,是一個整體;
原子性是拒絕多線程操作的,不論是多核還是單核,具有原子性的量,同一時刻只能有一個線程來對它進(jìn)行操作,所以synchronize可以保證變量原子性;
volatile能保證變量在私有內(nèi)存和主內(nèi)存間的同步,但是多個線程操作仍然可能打斷,所以不能保證變量的原子性;
2.可見性
volatile是變量在多線程之間的可見性(使得緩存數(shù)據(jù)無效),synchronize并不會馬上更新到主存里,只不過只有一個線程操作,數(shù)據(jù)繼續(xù)是緩存->主存;
3.有序性
都是一定程度保證有序性,volatile的有序性規(guī)則不細(xì)說,synchronize是保證同步代碼塊內(nèi)的代碼不會重排序到同步塊之外(其實(shí)也是monitor屏障)
4.其他方面
volatile是線程同步的輕量級實(shí)現(xiàn),多線程訪問volatile不會發(fā)生阻塞,而synchronize會發(fā)生阻塞,所以volatile的性能要比synchronize好;
volatile只能用于修飾變量,synchronize可以用于修飾方法、代碼塊,對于多線程訪問同一個實(shí)例變量還是需要加鎖同步。

鎖范圍

Java并發(fā)之synchronized深度解析
https://mp.weixin.qq.com/s/xPUKTIIwz4sU1cK1eWy21w

鎖思想
http://www.itdecent.cn/p/94cf9ebd8932
包含,悲觀(就是鎖上)樂觀(認(rèn)為不會修改,寫少的場景,無鎖,CAS操作)。
鎖升級: 自旋鎖(先不釋放鎖,下次申請鎖還是自己就不用再加鎖消耗),輕量鎖(等待,里面有個id,判斷到?jīng)]到自己),重量鎖(直接掛起等待喚醒)

結(jié)合單例DCL去講

餓漢法寫一個單例
https://blog.csdn.net/Jo__yang/article/details/52117031

https://mp.weixin.qq.com/s/N6UqsoWLEUWFFu2S_OT75w

那些年,我們一起寫的單例模式
https://mp.weixin.qq.com/s/pixuEDQ_OZ0RFjciTThKlQ

那為什么用enum寫單例是線程安全的?
http://www.hollischuang.com/archives/197


各種鎖示意圖

圖源https://mp.weixin.qq.com/s/tQ4dgerk9-FR4HIW5rwHpQ

ReentrantLock

reentrantLock重入鎖,—這個沒咋看,不常用
結(jié)合DelayQueue的take方法
https://blog.csdn.net/kobejayandy/article/details/46833623


樂觀悲觀

鎖繼續(xù)發(fā)散就是樂觀鎖和悲觀鎖
這兩種不是真正的鎖,而是一種思想,前面說的加鎖都是悲觀鎖,cas是一種樂觀鎖的實(shí)現(xiàn)形式。
悲觀鎖就是每個操作前都鎖定,樂觀鎖就是先讀取操作后再比較,適用于讀高發(fā)的場景。具體參考
CAS (compareAndSwap)
https://www.cnblogs.com/qjjazry/p/6581568.html


死鎖

死鎖的概念
代碼實(shí)現(xiàn)一個死鎖,如何解決死鎖?
http://cmsblogs.com/?p=1312


jvm 跟jmm的區(qū)別

jvm是指java虛擬機(jī)的內(nèi)部結(jié)構(gòu)模型,內(nèi)部包括: 線程共享方法區(qū)&堆區(qū),線程獨(dú)享?xiàng)^(qū),本地方法區(qū),程序計(jì)數(shù)器。

數(shù)據(jù)結(jié)構(gòu)的線程安全問題

有時候是從數(shù)據(jù)結(jié)構(gòu)深入到線程安全。
比如先問HashMap和LinkedHashMap的區(qū)別,然后問HashMap的缺點(diǎn),
再對比HashMap和HashTable,Collection.SynchornizedMap的優(yōu)缺,提出改進(jìn)方案用ConcurrentHashMap--->結(jié)構(gòu)特點(diǎn)

這里再發(fā)散一下
有點(diǎn)像ArrayList和LinkedList比較,再比較Vector,和CopyOnWriteArrayList


多線程中的通信問題

就是考Handler的消息機(jī)制

首先handler收發(fā)消息的圖要會畫

然后通過各自的構(gòu)造方法去梳理一下

handler/thread/threadLocal/looper/messageQueue 的對應(yīng)關(guān)系

以activity在主線程啟動的為例(handler有關(guān)的)

1.ActivityThread的main方法里L(fēng)ooper.prepareMainLooper(就是prepare方法);
2.activityThread就綁定了這個線程,雖然我不知道跟這個handler有啥用;
3.并且主動調(diào)用loop循環(huán)取消息;

prepareLooper方法做了兩件事

1.判斷當(dāng)前線程的threadLocal里有沒有l(wèi)ooper,有就報錯,因?yàn)橐粋€線程里只能有一個looper;
2.threadLocal(looper類的餓漢初始化)里沒有l(wèi)ooper,則初始化looper并set進(jìn)去;
這里細(xì)節(jié)
Looper prepare的時候,
先檢查 threadLocal,先get,看能不能拿到looper
再set,把looper set到threadLocal里
ThreadLocal 的get方法,很詭異
看起來是key,value,key是thread,value是looper

實(shí)際上是獲取 thread里私有的threadLocal,
因?yàn)門hreadLocal.ThreadLocalMap是線程私有的。
這個ThreadLocalMap是ThreadLocal的靜態(tài)內(nèi)部類。

然后并不是真正的map。而是一個 Entry數(shù)組。
這個數(shù)組存的方式也很奇怪,index是threadLocal的hashCode
value是Entry。
entry的key是 ThreadLocal,value是其持有的私有對象值,這里是looper

然后,這個entry,繼承的是一個WeakReference<ThreadLocal<?>>
這個能保證,weakReference里的threadLocal 就不算強(qiáng)引用,可以正常回收,threadlocalMap也就可以回收,這樣里面的一些私有變量就可以被回收。

舉個例子, userInfo放到threadLocal里,做線程私有變量,當(dāng)userInfo不用了,userInfo==null,
但是threadLocal還持有userInfo(集合持有),threadLocal生命周期跟thread一起,可能這個thread就一直沒釋放,也就沒回收,導(dǎo)致內(nèi)部的threadLocalMap就一直保留著userInfo.

這里就可以做到,threadLocal的remove函數(shù),會主動清理掉entry,避免集合引用導(dǎo)致的泄露。

這里可以看這篇文章
https://mp.weixin.qq.com/s/8Emvbmbn1CgBPjddETEjMA

Looper的初始化方法里做了兩件事

1.綁定一個messageQueue,所以一個looper只有一個msgQueue;
2.綁定當(dāng)前所處線程;
所以主線程默認(rèn)是有一個looper,一個msgQueue,主線程的handler是不需要額外prepare的

然后就是1個線程可以有多個handler(實(shí)際就是1個looper可以有多個handler)參考主線程,其余都是一一擁有關(guān)系。

收發(fā)消息

handler.sendMsg/obtainMsg的區(qū)別

handler怎樣做到MsgDelay

->handler.sendMsgDelay
->msg內(nèi)部存儲了一個時間戳
->sendMessageAtTime方法綁定一個msgQueue(也從側(cè)面說明,一個handler只能同一時間處理一個msgQueue的消息)
->handler.enqueueMessage
->msgQueue.enqueueMessage
->內(nèi)部有個死循環(huán)來判斷排隊(duì)

這里經(jīng)常會被問到為什么msg delay不會被阻塞,比如sendMsgDelay ,第一個msg delay 30s,第二個msg delay 5s
那么第二個msg真的會在第35s才被接收嗎?
這里涉及到的就是msg的阻塞問題,

實(shí)際上結(jié)合MessageQueue的enquene方法,和msg的next方法,內(nèi)部維護(hù)一個needAwake
首先判斷的是消息是否退出,如果要退出就回收消息,然后獲取到正在使用的消息并將傳遞過來的時間(即延時時間)賦值給msg.when。而p又是什么?其實(shí)p = mMessages,第一次進(jìn)來p是null,接著判斷消息是否為null,當(dāng)前消息的when是否為0或者小于p.when滿足這個條件,p 就是下一個要執(zhí)行的消息即(msg.next),再給needWake賦值時刻喚醒消息。如果不滿足上面的條件(即線程阻塞了)則開啟一個死循環(huán)將其插入到隊(duì)列中,讓when = 0的消息或者when < p.when(即延時小的消息)先執(zhí)行。

loop方法的內(nèi)部實(shí)現(xiàn)

內(nèi)部一個死循環(huán)不斷去取msg,取到之后msg.target.dispatchMsg,這個msg.target本質(zhì)是handler對象,
在handler.obtain的時候,msg就綁定上了一個target,handler.send的是在equeueMsg的時候綁上的,最后由這個handler去handleMsg

這里會被問到,為什么這個loop的死循環(huán)不會阻塞UI線程?
在MessageQueue.next()方法里,會調(diào)用一個native方法:nativePollOnce(long ptr, int timeoutMillis),當(dāng)主線程沒有消息可處理的時候,該方法會阻塞主線程。在這種情況下,用戶點(diǎn)擊一下屏幕
nativePollOnce()方法繼續(xù)執(zhí)行了,并且調(diào)用了InputEventReceiver.dispatchInputEvent()
調(diào)用nativePollOnce()方法掛起主線程之后,當(dāng)有一些事件到來時,native層會喚醒主線程
https://www.zhihu.com/question/34652589

IntentService/HandlerThread的學(xué)習(xí)與使用

http://www.itdecent.cn/p/b9427d4011e0

ThreadLocal

面試必問:Android Handler機(jī)制之ThreadLocal
https://mp.weixin.qq.com/s/WOYUWPSSrGIaX1uuboRrqg

ThreadLocal解析
https://mp.weixin.qq.com/s/VNBwPPJeENyU9iyxJcN1qw


AsyncTask

使用
http://www.itdecent.cn/p/7d2fa022b647

源碼
http://www.itdecent.cn/p/97da1c0f21c4

強(qiáng)烈推薦看這個
關(guān)于創(chuàng)建ayncTask的
https://mp.weixin.qq.com/s/5CeZ6NHF6dm3qN6RgzaGDQ


其他的同步

Condition 與傳統(tǒng)多線程協(xié)作區(qū)別問題解析
https://mp.weixin.qq.com/s/naUaxbcenTZeiQ441wyQbA

Android Exchanger
https://mp.weixin.qq.com/s/bOFsVALa8oBbhvcrzCIbYw


進(jìn)程

Android技能樹 — 多進(jìn)程相關(guān)小結(jié)
https://mp.weixin.qq.com/s/DgMXCqRh5sQiIDEUeTPzcw


參考鏈接

android 多線程 — 綜述
http://www.itdecent.cn/p/52d752dac4aa

還有一些線程的問題
https://mp.weixin.qq.com/s/QQt8sPOjsqQ0kHtfGsSlMg

JAVA多線程和并發(fā)基礎(chǔ)面試問答
http://blog.jobbole.com/76308/

Java線程面試題 Top 50
http://www.importnew.com/12773.html

40個Java多線程問題總結(jié)
http://www.cnblogs.com/xrq730/p/5060921.html

handler實(shí)現(xiàn)原理,從源碼分析
http://www.2cto.com/kf/201605/507567.html

多線程的實(shí)際應(yīng)用例子
簡易斷點(diǎn)續(xù)傳下載器實(shí)現(xiàn)
http://www.itdecent.cn/p/5b2e22c42467

Android多線程斷點(diǎn)續(xù)傳下載
http://www.itdecent.cn/p/2b82db0a5181

Android里怎么回主線程操作
https://www.cnblogs.com/jingmo0319/p/5730963.html

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

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

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