目錄
- 線程狀態(tài)
- 線程池
- 線程安全
- Java Memory Model
- Volatile
- Sychornized
- ReentrantLock
- 樂觀悲觀
- 死鎖
- jmm和jvm的區(qū)別
- 線程通信handler機制
- IntentService
- ThreadLocal
- 原子類(沒寫)
- AsyncTask
- 其他的同步
- 進程
- 參考鏈接
線程狀態(tài)
線程狀態(tài)轉換圖
new,runnable,running,dead,blocked,waiting/time waiting
狀態(tài)轉換的幾個方法及其比較
start()/run()的區(qū)別:
start是讓線程處于就緒狀態(tài)真正的實現(xiàn)多線程,內部是調用了run方法。而run方法不是洋丐,繼續(xù)同步調用吊圾。
其余幾個方法的比較:
yield—線程靜態(tài)方法,釋放鎖麻蹋,放棄這次cpu機會跛溉,下次再競爭,只給不比自己優(yōu)先級低的線程機會扮授,執(zhí)行后線程進入runnable狀態(tài)
sleep--靜態(tài)方法芳室,不釋放鎖,不考慮優(yōu)先級刹勃,執(zhí)行后進入block狀態(tài)堪侯,有的也說進入timewaiting狀態(tài),從不釋放鎖這點來看荔仁,我覺得是block狀態(tài)伍宦。
join--實例方法,使該實例進程阻塞乏梁,當被join的線程執(zhí)行完才執(zhí)行自己次洼,執(zhí)行進入wait/timewaiting狀態(tài)
wait—Object類實例方法,釋放鎖遇骑,必須放在同步塊里使用卖毁,自己則進入對象等待池,執(zhí)行后進入wait/timewaiting落萎,wait(time)就是進入timewaiting狀態(tài)亥啦,能提前notify么?
notify--實例方法练链,該實例對象移除對象等待池翔脱,進入鎖標志等待池
notifyAll--實例方法,喚醒所有的線程媒鼓,讓其競爭上崗
怎樣使用多線程
繼承thread届吁,實現(xiàn)runnable接口,實現(xiàn)callable接口
線程池
線程池ThreadPool的使用executorService.excute绿鸣,
根據(jù)其構造方法理解線程池
比如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)化點就在減少線程的創(chuàng)建和銷毀時間,開發(fā)者更多的關注任務
構造方法的四個隊列及其特點
linkedBlockQueue再登,ArrayBlockQueue尔邓,DelayQueue晾剖,SynchronizedQueue
Okhttp的內部實現(xiàn)
可以借助OkHttpClient.newCall(request).equeue(Callback)來理解線程池的應用
參考okhttp章節(jié)的發(fā)送請求段落
http://www.reibang.com/p/6fa13048a6cf
線程安全問題
實質就是多線程的同步問題。
為什么會出現(xiàn)同步問題梯嗽,背景在數(shù)據(jù)讀寫這里齿尽,先看到讀寫方式(如圖)
1.程序運行過程中的臨時數(shù)據(jù)是存放在主存(物理內存)當中的;
cpu訪問速度和順序:register寄存器-->cpu cache mem(高速緩存)-->RAM 主存
2.由于CPU執(zhí)行速度很快灯节,而從內存讀取數(shù)據(jù)和向內存寫入數(shù)據(jù)的過程跟CPU執(zhí)行指令的速度比起來要慢的多循头;
3.如果任何時候對數(shù)據(jù)的操作都要通過和內存的交互來進行,會大大降低指令執(zhí)行的速度炎疆;
4.所以最終在CPU里面就有了高速緩存卡骂;
線程會從主存(堆區(qū))copy一份到棧內存里(高速緩存里),處理完后就把這個副本刷回去
5.但是問題在形入,有緩存不一致的問題全跨,也就是多線程訪問會訪問到緩存,而不是實時數(shù)據(jù)亿遂;
6.于是有了加鎖的概念(線程安全的問題)浓若。
Java Memory Model
解決線程安全模型,先來了解jmm
什么是Java內存模型蛇数?
https://mp.weixin.qq.com/s/_4AtyCVPj6E4AkHzVHsKbg
http://www.cnblogs.com/dolphin0520/p/3920373.html
因此并發(fā)可能遇到的三個問題
1.原子性:即一個操作或者多個操作 要么全部執(zhí)行并且執(zhí)行的過程不會被任何因素打斷挪钓,要么就都不執(zhí)行;
2.可見性:是指當多個線程訪問同一個變量時,一個線程修改了這個變量的值耳舅,其他線程能夠立即看得到修改的值;
3.有序性:即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行碌上。
再更詳細的說
java內存模型的毛病
http://cmsblogs.com/?p=2161
實例化一個對象要分為三個步驟:
1.分配內存空間
2.初始化對象
3.將內存空間的地址賦值給對應的引用
但是由于重排序的緣故,步驟2挽放、3可能會發(fā)生重排序绍赛,其過程如下:
1.分配內存空間
2.將內存空間的地址賦值給對應的引用
3.初始化對象
如果2蔓纠、3發(fā)生了重排序就會導致第二個判斷會出錯辑畦,singleton != null,但是它其實僅僅只是一個地址而已腿倚,
此時對象還沒有被初始化纯出,所以return的singleton對象是一個沒有被初始化的對象
兩個解決辦法:
1.不允許初始化階段步驟2 、3發(fā)生重排序敷燎。
2.允許初始化階段步驟2 暂筝、3發(fā)生重排序,但是不允許其他線程“看到”這個重排序硬贯。
Volatile
volatile關鍵字焕襟,用來修飾變量
volatile在以下三方面影響java內存模型,從而保證線程安全
1.原子性:Java中對基本數(shù)據(jù)類型的變量的讀取和賦值操作是原子性操作饭豹,即這些操作是不可被中斷的鸵赖,要么執(zhí)行务漩,要么不執(zhí)行,其余都是非原子操作;
*volatile不保證原子性操作,所以對原子性沒幫助
2.可見性:Java里的一個共享變量被volatile修飾時它褪,它會保證修改的值會立即被更新到主存饵骨,當有其他線程需要讀取時,它會去內存中讀取新值茫打;
*volatile保證修改的值會立即被更新到主存
3.有序性:JavaJ內存模型中居触,允許編譯器和處理器對指令進行重排序,但是重排序過程不會影響到單線程程序的執(zhí)行老赤,卻會影響到多線程并發(fā)執(zhí)行的正確性轮洋;
*volatile原則屬于 happens-before原則的一部分,能部分保證有序性
從匯編角度了解volatile禁止重排序诗越,了解happens-before原則
http://cmsblogs.com/?p=2148
Synchronize
Synchronize關鍵字砖瞧,本身不是鎖,只是去拿鎖
從jmm角度分析synchronize和volatile的區(qū)別
1.原子性
代表了原子性的操作(幾個步驟是一個原子)在線程執(zhí)行過程中不會被中斷嚷狞,是一個整體块促;
原子性是拒絕多線程操作的,不論是多核還是單核床未,具有原子性的量竭翠,同一時刻只能有一個線程來對它進行操作,所以synchronize可以保證變量原子性薇搁;
volatile能保證變量在私有內存和主內存間的同步斋扰,但是多個線程操作仍然可能打斷,所以不能保證變量的原子性啃洋;
2.可見性
volatile是變量在多線程之間的可見性(使得緩存數(shù)據(jù)無效)传货,synchronize并不會馬上更新到主存里,只不過只有一個線程操作,數(shù)據(jù)繼續(xù)是緩存->主存宏娄;
3.有序性
都是一定程度保證有序性问裕,volatile的有序性規(guī)則不細說,synchronize是保證同步代碼塊內的代碼不會重排序到同步塊之外(其實也是monitor屏障)
4.其他方面
volatile是線程同步的輕量級實現(xiàn)孵坚,多線程訪問volatile不會發(fā)生阻塞粮宛,而synchronize會發(fā)生阻塞,所以volatile的性能要比synchronize好卖宠;
volatile只能用于修飾變量巍杈,synchronize可以用于修飾方法、代碼塊扛伍,對于多線程訪問同一個實例變量還是需要加鎖同步筷畦。
鎖范圍
Java并發(fā)之synchronized深度解析
https://mp.weixin.qq.com/s/xPUKTIIwz4sU1cK1eWy21w
鎖思想
http://www.reibang.com/p/94cf9ebd8932
包含,悲觀(就是鎖上)樂觀(認為不會修改刺洒,寫少的場景鳖宾,無鎖亚斋,CAS操作)。
鎖升級: 自旋鎖(先不釋放鎖攘滩,下次申請鎖還是自己就不用再加鎖消耗)帅刊,輕量鎖(等待,里面有個id漂问,判斷到?jīng)]到自己)赖瞒,重量鎖(直接掛起等待喚醒)
結合單例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
各種鎖示意圖
ReentrantLock
reentrantLock重入鎖栏饮,—這個沒咋看,不常用
結合DelayQueue的take方法
https://blog.csdn.net/kobejayandy/article/details/46833623
樂觀悲觀
鎖繼續(xù)發(fā)散就是樂觀鎖和悲觀鎖
這兩種不是真正的鎖磷仰,而是一種思想袍嬉,前面說的加鎖都是悲觀鎖,cas是一種樂觀鎖的實現(xiàn)形式灶平。
悲觀鎖就是每個操作前都鎖定伺通,樂觀鎖就是先讀取操作后再比較,適用于讀高發(fā)的場景逢享。具體參考
CAS (compareAndSwap)
https://www.cnblogs.com/qjjazry/p/6581568.html
死鎖
死鎖的概念
代碼實現(xiàn)一個死鎖罐监,如何解決死鎖?
http://cmsblogs.com/?p=1312
jvm 跟jmm的區(qū)別
jvm是指java虛擬機的內部結構模型瞒爬,內部包括: 線程共享方法區(qū)&堆區(qū)弓柱,線程獨享棧區(qū),本地方法區(qū)侧但,程序計數(shù)器矢空。
數(shù)據(jù)結構的線程安全問題
有時候是從數(shù)據(jù)結構深入到線程安全。
比如先問HashMap和LinkedHashMap的區(qū)別禀横,然后問HashMap的缺點屁药,
再對比HashMap和HashTable,Collection.SynchornizedMap的優(yōu)缺燕侠,提出改進方案用ConcurrentHashMap--->結構特點
這里再發(fā)散一下
有點像ArrayList和LinkedList比較者祖,再比較Vector立莉,和CopyOnWriteArrayList
多線程中的通信問題
就是考Handler的消息機制
首先handler收發(fā)消息的圖要會畫
然后通過各自的構造方法去梳理一下
handler/thread/threadLocal/looper/messageQueue 的對應關系
以activity在主線程啟動的為例(handler有關的)
1.ActivityThread的main方法里Looper.prepareMainLooper(就是prepare方法)绢彤;
2.activityThread就綁定了這個線程,雖然我不知道跟這個handler有啥用蜓耻;
3.并且主動調用loop循環(huán)取消息茫舶;
prepareLooper方法做了兩件事
1.判斷當前線程的threadLocal里有沒有l(wèi)ooper,有就報錯刹淌,因為一個線程里只能有一個looper饶氏;
2.threadLocal(looper類的餓漢初始化)里沒有l(wèi)ooper讥耗,則初始化looper并set進去;
這里細節(jié)
Looper prepare的時候疹启,
先檢查 threadLocal古程,先get,看能不能拿到looper
再set喊崖,把looper set到threadLocal里
ThreadLocal 的get方法挣磨,很詭異
看起來是key,value荤懂,key是thread茁裙,value是looper
實際上是獲取 thread里私有的threadLocal,
因為ThreadLocal.ThreadLocalMap是線程私有的节仿。
這個ThreadLocalMap是ThreadLocal的靜態(tài)內部類晤锥。
然后并不是真正的map。而是一個 Entry數(shù)組廊宪。
這個數(shù)組存的方式也很奇怪矾瘾,index是threadLocal的hashCode
value是Entry。
entry的key是 ThreadLocal箭启,value是其持有的私有對象值霜威,這里是looper
然后,這個entry册烈,繼承的是一個WeakReference<ThreadLocal<?>>
這個能保證戈泼,weakReference里的threadLocal 就不算強引用,可以正成蜕回收大猛,threadlocalMap也就可以回收,這樣里面的一些私有變量就可以被回收淀零。
舉個例子挽绩, userInfo放到threadLocal里,做線程私有變量驾中,當userInfo不用了唉堪,userInfo==null,
但是threadLocal還持有userInfo(集合持有)肩民,threadLocal生命周期跟thread一起唠亚,可能這個thread就一直沒釋放,也就沒回收持痰,導致內部的threadLocalMap就一直保留著userInfo.
這里就可以做到灶搜,threadLocal的remove函數(shù),會主動清理掉entry,避免集合引用導致的泄露割卖。
這里可以看這篇文章
https://mp.weixin.qq.com/s/8Emvbmbn1CgBPjddETEjMA
Looper的初始化方法里做了兩件事
1.綁定一個messageQueue前酿,所以一個looper只有一個msgQueue;
2.綁定當前所處線程鹏溯;
所以主線程默認是有一個looper罢维,一個msgQueue,主線程的handler是不需要額外prepare的
然后就是1個線程可以有多個handler(實際就是1個looper可以有多個handler)參考主線程丙挽,其余都是一一擁有關系言津。
收發(fā)消息
handler.sendMsg/obtainMsg的區(qū)別
handler怎樣做到MsgDelay
->handler.sendMsgDelay
->msg內部存儲了一個時間戳
->sendMessageAtTime方法綁定一個msgQueue(也從側面說明,一個handler只能同一時間處理一個msgQueue的消息)
->handler.enqueueMessage
->msgQueue.enqueueMessage
->內部有個死循環(huán)來判斷排隊
這里經(jīng)常會被問到為什么msg delay不會被阻塞取试,比如sendMsgDelay 悬槽,第一個msg delay 30s,第二個msg delay 5s
那么第二個msg真的會在第35s才被接收嗎瞬浓?
這里涉及到的就是msg的阻塞問題初婆,
實際上結合MessageQueue的enquene方法,和msg的next方法猿棉,內部維護一個needAwake
首先判斷的是消息是否退出磅叛,如果要退出就回收消息,然后獲取到正在使用的消息并將傳遞過來的時間(即延時時間)賦值給msg.when萨赁。而p又是什么弊琴?其實p = mMessages,第一次進來p是null杖爽,接著判斷消息是否為null敲董,當前消息的when是否為0或者小于p.when滿足這個條件,p 就是下一個要執(zhí)行的消息即(msg.next),再給needWake賦值時刻喚醒消息慰安。如果不滿足上面的條件(即線程阻塞了)則開啟一個死循環(huán)將其插入到隊列中腋寨,讓when = 0的消息或者when < p.when(即延時小的消息)先執(zhí)行。
loop方法的內部實現(xiàn)
內部一個死循環(huán)不斷去取msg化焕,取到之后msg.target.dispatchMsg萄窜,這個msg.target本質是handler對象,
在handler.obtain的時候撒桨,msg就綁定上了一個target查刻,handler.send的是在equeueMsg的時候綁上的,最后由這個handler去handleMsg
這里會被問到凤类,為什么這個loop的死循環(huán)不會阻塞UI線程穗泵?
在MessageQueue.next()方法里,會調用一個native方法:nativePollOnce(long ptr, int timeoutMillis)踱蠢,當主線程沒有消息可處理的時候火欧,該方法會阻塞主線程。在這種情況下茎截,用戶點擊一下屏幕
nativePollOnce()方法繼續(xù)執(zhí)行了苇侵,并且調用了InputEventReceiver.dispatchInputEvent()
調用nativePollOnce()方法掛起主線程之后,當有一些事件到來時企锌,native層會喚醒主線程
https://www.zhihu.com/question/34652589
IntentService/HandlerThread的學習與使用
http://www.reibang.com/p/b9427d4011e0
ThreadLocal
面試必問:Android Handler機制之ThreadLocal
https://mp.weixin.qq.com/s/WOYUWPSSrGIaX1uuboRrqg
ThreadLocal解析
https://mp.weixin.qq.com/s/VNBwPPJeENyU9iyxJcN1qw
AsyncTask
使用
http://www.reibang.com/p/7d2fa022b647
源碼
http://www.reibang.com/p/97da1c0f21c4
強烈推薦看這個
關于創(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
進程
Android技能樹 — 多進程相關小結
https://mp.weixin.qq.com/s/DgMXCqRh5sQiIDEUeTPzcw
參考鏈接
android 多線程 — 綜述
http://www.reibang.com/p/52d752dac4aa
還有一些線程的問題
https://mp.weixin.qq.com/s/QQt8sPOjsqQ0kHtfGsSlMg
JAVA多線程和并發(fā)基礎面試問答
http://blog.jobbole.com/76308/
Java線程面試題 Top 50
http://www.importnew.com/12773.html
40個Java多線程問題總結
http://www.cnblogs.com/xrq730/p/5060921.html
handler實現(xiàn)原理榆浓,從源碼分析
http://www.2cto.com/kf/201605/507567.html
多線程的實際應用例子
簡易斷點續(xù)傳下載器實現(xiàn)
http://www.reibang.com/p/5b2e22c42467
Android多線程斷點續(xù)傳下載
http://www.reibang.com/p/2b82db0a5181
Android里怎么回主線程操作
https://www.cnblogs.com/jingmo0319/p/5730963.html