Java基礎(chǔ)知識(shí)
String a = "aaa" 和 String a = new String("aaa")
"aaa"盲再,這個(gè)本身就是一個(gè)字符串對象梦碗,字符串對象是一個(gè)常量循捺,一旦初始化恰力,就不可更改阵漏,存在于內(nèi)存常量池中叹洲。
前面String a = "aaa" 的意思就是在棧中創(chuàng)建一個(gè)字符串類型的變量a民泵,
所以String a = "aa"的意思就是將字符串對象"aa"的內(nèi)存地址賦給字符串變量a
String a = new String("aa")中有兩個(gè)對象,一個(gè)是"aa"同上,另一個(gè)則是new String()拾给,此對象創(chuàng)建時(shí)通過構(gòu)造函數(shù)用字符串"aa"進(jìn)行初始化额衙,這兩個(gè)對象內(nèi)存地址是不一樣的
Android基礎(chǔ)
Activity View Window 的理解
Activity像一個(gè)工匠(控制單元)硼啤,Window像窗戶(承載模型),View像窗花(顯示視圖) LayoutInflater像剪刀斧账,Xml配置像窗花圖紙咧织。Activity其實(shí)不是顯示視圖习绢,View才是真正的顯示視圖为迈。但是View不能單獨(dú)存在伴郁,它必須附著在Window這個(gè)抽象的概念上面,因此有視圖的地方就有Window狈涮。
- 一個(gè)Activity構(gòu)造的時(shí)候會(huì)初始化一個(gè)Window狐胎,準(zhǔn)確的說是PhoneWindow。
- 這個(gè)PhoneWindow有一個(gè)“ViewRoot”歌馍,引號(hào)是說其實(shí)這個(gè)“ViewRoot”是一個(gè)View或者說ViewGroup握巢,是最初始的根視圖。
- “ViewRoot”通過addView方法來一個(gè)個(gè)的添加View松却。比如TextView暴浦,Button等
- 這些View的事件監(jiān)聽,是由WindowManagerService來接受消息晓锻,并且回調(diào)Activity函數(shù)歌焦。比如onClickListener,onKeyDown等
Context 理解
Context的中文翻譯為:語境; 上下文; 背景; 環(huán)境砚哆,在開發(fā)中我們經(jīng)常說稱之為“上下文”独撇。一個(gè)Activity就是一個(gè)Context,一個(gè)Service也是一個(gè)Context躁锁。
Context類本身是一個(gè)純abstract類券勺,它有兩個(gè)具體的實(shí)現(xiàn)子類:ContextImpl和ContextWrapper。其中ContextWrapper類只是一個(gè)包裝而已灿里,提供attachBaseContext()用于給ContextWrapper對象中指定真正的Context對象,這個(gè)baseContext就是ContextImpl.
ContextImpl類則真正實(shí)現(xiàn)了Context中的所以函數(shù)程腹,應(yīng)用程序中所調(diào)用的各種Context類的方法匣吊,其實(shí)現(xiàn)均來自于該類。一句話總結(jié):Context的兩個(gè)子類分工明確寸潦,其中ContextImpl是Context的具體實(shí)現(xiàn)類色鸳,ContextWrapper是Context的包裝類。Activity见转,Application命雀,Service雖都繼承自ContextWrapper(Activity繼承自ContextWrapper的子類ContextThemeWrapper),但它們初始化的過程中都會(huì)創(chuàng)建ContextImpl對象斩箫,由ContextImpl實(shí)現(xiàn)Context中的方法吏砂。
一個(gè)應(yīng)用程序 Context數(shù)量=Activity數(shù)量+Service數(shù)量+1
getApplication()和getApplicationContext()指向的是同一塊內(nèi)存地址撵儿,也就說他們返回同一個(gè)對象
Context 作用域:凡是跟UI相關(guān)的,都應(yīng)該使用Activity做為Context來處理狐血;其他的一些操作淀歇,Service,Activity,Application等實(shí)例都可以,當(dāng)然了匈织,注意Context引用的持有浪默,防止內(nèi)存泄漏。
使用Context的正確姿勢:
- 當(dāng)Application的Context能搞定的情況下缀匕,并且生命周期長的對象纳决,優(yōu)先使用Application的Context。
- 不要讓生命周期長于Activity的對象持有到Activity的引用乡小。
- 盡量不要在Activity中使用非靜態(tài)內(nèi)部類阔加,因?yàn)榉庆o態(tài)內(nèi)部類會(huì)隱式持有外部類實(shí)例的引用,如果使用靜態(tài)內(nèi)部類劲件,將外部實(shí)例引用作為弱引用持有掸哑。
Activity Launch Mode
- standard: 每一個(gè)intent創(chuàng)建一個(gè)Activity處理
- singleTop: 只有在調(diào)用者和目標(biāo)Activity在同一Task中,并且目標(biāo)Activity位于棧頂零远,才使用現(xiàn)有目標(biāo)Activity實(shí)例苗分,否則創(chuàng)建新的目標(biāo)Activity實(shí)例。如果是外部程序啟動(dòng)singleTop的Activity牵辣,在Android 5.0之前新創(chuàng)建的Activity會(huì)位于調(diào)用者的Task中摔癣,5.0及以后會(huì)放入新的Task中。
- singleTask: 使用singleTask啟動(dòng)模式的Activity在系統(tǒng)中只會(huì)存在一個(gè)實(shí)例纬向。如果singleTask Activity實(shí)例已然存在择浊,那么在Activity回退棧中,所有位于該Activity上面的Activity實(shí)例都將被銷毀掉(銷毀過程會(huì)調(diào)用Activity生命周期回調(diào))逾条,這樣使得singleTask Activity實(shí)例位于棧頂琢岩。與此同時(shí),Intent會(huì)通過onNewIntent傳遞到這個(gè)SingleTask Activity實(shí)例师脂。
- singleInstance: 這個(gè)模式和singleTask差不多担孔,因?yàn)樗麄冊谙到y(tǒng)中都只有一份實(shí)例。唯一不同的就是存放singleInstance Activity實(shí)例的Task只能存放一個(gè)該模式的Activity實(shí)例吃警。如果從singleInstance Activity實(shí)例啟動(dòng)另一個(gè)Activity糕篇,那么這個(gè)Activity實(shí)例會(huì)放入其他的Task中。同理酌心,如果singleInstance Activity被別的Activity啟動(dòng)拌消,它也會(huì)放入不同于調(diào)用者的Task中。
Activity 和Fragment的理解
Fragment是在Android 3.0版本上開始出現(xiàn)安券,主要是解決手機(jī)和平板等折本上適配墩崩,多個(gè)Activity之間切換是性能的問題等氓英。
Fragment更多的生命周期。
onAttach onCreateView onViewCreated onActivityCreated onDestoryView onDetach
Activity和Fragment之間的通訊
- 廣播機(jī)制: 太重泰鸡,有延時(shí)
- 接口回調(diào)
- Event Bus: 性能問題
- 直接類方法調(diào)用
Fragment 遇到的坑:
- 多個(gè)Fragment疊加時(shí) 頂層空白處點(diǎn)擊會(huì)造成后面View響應(yīng): 原因是因?yàn)镕ragment添加進(jìn)去后其實(shí)就是添加的View债蓝,頂層View未消費(fèi)點(diǎn)擊事件就會(huì)傳遞到底層的View上∈⒘洌可以在跟layout上添加
clickable="true"
消費(fèi)掉該事件
內(nèi)存泄漏有哪些場景以及解決方法
- 類的靜態(tài)變量持有大數(shù)據(jù)對象 靜態(tài)變量長期維持到大數(shù)據(jù)對象的引用饰迹,阻止垃圾回收。
- 非靜態(tài)內(nèi)部類存在靜態(tài)實(shí)例 非靜態(tài)內(nèi)部類會(huì)維持一個(gè)到外部類實(shí)例的引用余舶,如果非靜態(tài)內(nèi)部類的實(shí)例是靜態(tài)的啊鸭,就會(huì)間接長期維持著外部類的引用,阻止被回收掉匿值。
- 資源對象未關(guān)閉 資源性對象比如(Cursor赠制,F(xiàn)ile文件等)往往都用了一些緩沖,我們在不使用的時(shí)候挟憔,應(yīng)該及時(shí)關(guān)閉它們钟些, 以便它們的緩沖及時(shí)回收內(nèi)存。它們的緩沖不僅存在于java虛擬機(jī)內(nèi)绊谭,還存在于java虛擬機(jī)外政恍。 如果我們僅僅是把它的引用設(shè)置為null,而不關(guān)閉它們,往往會(huì)造成內(nèi)存泄露达传。 解決辦法: 比如SQLiteCursor(在析構(gòu)函數(shù)finalize(),如果我們沒有關(guān)閉它篙耗,它自己會(huì)調(diào)close()關(guān)閉), 如果我們沒有關(guān)閉它宪赶,系統(tǒng)在回收它時(shí)也會(huì)關(guān)閉它宗弯,但是這樣的效率太低了。 因此對于資源性對象在不使用的時(shí)候搂妻,應(yīng)該調(diào)用它的close()函數(shù)蒙保,將其關(guān)閉掉,然后才置為null. 在我們的程序退出時(shí)一定要確保我們的資源性對象已經(jīng)關(guān)閉欲主。 程序中經(jīng)常會(huì)進(jìn)行查詢數(shù)據(jù)庫的操作追他,但是經(jīng)常會(huì)有使用完畢Cursor后沒有關(guān)閉的情況。如果我們的查詢結(jié)果集比較小岛蚤, 對內(nèi)存的消耗不容易被發(fā)現(xiàn),只有在常時(shí)間大量操作的情況下才會(huì)復(fù)現(xiàn)內(nèi)存問題懈糯,這樣就會(huì)給以后的測試和問題排查帶來困難和風(fēng)險(xiǎn)涤妒,記得try catch后,在finally方法中關(guān)閉連接
- Handler內(nèi)存泄漏 Handler作為內(nèi)部類存在于Activity中赚哗,但是Handler生命周期與Activity生命周期往往并不是相同的她紫,比如當(dāng)Handler對象有Message在排隊(duì)硅堆,則無法釋放,進(jìn)而導(dǎo)致本該釋放的Acitivity也沒有辦法進(jìn)行回收贿讹。 解決辦法:聲明handler為static類渐逃,這樣內(nèi)部類就不再持有外部類的引用了,就不會(huì)阻塞Activity的釋放民褂;如果內(nèi)部類實(shí)在需要用到外部類的對象茄菊,可在其內(nèi)部聲明一個(gè)弱引用引用外部類。
- 一些不良代碼習(xí)慣 有些代碼并不造成內(nèi)存泄露赊堪,但是他們的資源沒有得到重用面殖,頻繁的申請內(nèi)存和銷毀內(nèi)存,消耗CPU資源的同時(shí)哭廉,也引起內(nèi)存抖動(dòng) 解決方案 如果需要頻繁的申請內(nèi)存對象和和釋放對象脊僚,可以考慮使用對象池來增加對象的復(fù)用。 例如ListView便是采用這種思想遵绰,通過復(fù)用converview來避免頻繁的GC
如何避免 OOM 問題的出現(xiàn)
- 使用更加輕量的數(shù)據(jù)結(jié)構(gòu) 例如辽幌,我們可以考慮使用ArrayMap/SparseArray而不是HashMap等傳統(tǒng)數(shù)據(jù)結(jié)構(gòu)。通常的HashMap的實(shí)現(xiàn)方式更加消耗內(nèi)存椿访,因?yàn)樗枰粋€(gè)額外的實(shí)例對象來記錄Mapping操作乌企。另外,SparseArray更加高效赎离,在于他們避免了對key與value的自動(dòng)裝箱(autoboxing)逛犹,并且避免了裝箱后的解箱。
- 避免在Android里面使用Enum Android官方培訓(xùn)課程提到過“Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.”梁剔,具體原理請參考《Android性能優(yōu)化典范(三)》虽画,所以請避免在Android里面使用到枚舉。
- 減小Bitmap對象的內(nèi)存占用 Bitmap是一個(gè)極容易消耗內(nèi)存的大胖子荣病,減小創(chuàng)建出來的Bitmap的內(nèi)存占用可謂是重中之重码撰,,通常來說有以下2個(gè)措施: inSampleSize:縮放比例个盆,在把圖片載入內(nèi)存之前脖岛,我們需要先計(jì)算出一個(gè)合適的縮放比例,避免不必要的大圖載入颊亮。 decode format:解碼格式柴梆,選擇ARGB_6666/RBG_545/ARGB_4444/ALPHA_6,存在很大差異
- Bitmap對象的復(fù)用 縮小Bitmap的同時(shí)终惑,也需要提高BitMap對象的復(fù)用率绍在,避免頻繁創(chuàng)建BitMap對象,復(fù)用的方法有以下2個(gè)措施 LRUCache : “最近最少使用算法”在Android中有極其普遍的應(yīng)用。ListView與GridView等顯示大量圖片的控件里偿渡,就是使用LRU的機(jī)制來緩存處理好的Bitmap臼寄,把近期最少使用的數(shù)據(jù)從緩存中移除,保留使用最頻繁的數(shù)據(jù)溜宽, inBitMap高級(jí)特性:利用inBitmap的高級(jí)特性提高Android系統(tǒng)在Bitmap分配與釋放執(zhí)行效率吉拳。使用inBitmap屬性可以告知Bitmap解碼器去嘗試使用已經(jīng)存在的內(nèi)存區(qū)域,新解碼的Bitmap會(huì)嘗試去使用之前那張Bitmap在Heap中所占據(jù)的pixel data內(nèi)存區(qū)域适揉,而不是去問內(nèi)存重新申請一塊區(qū)域來存放Bitmap留攒。利用這種特性,即使是上千張的圖片涡扼,也只會(huì)僅僅只需要占用屏幕所能夠顯示的圖片數(shù)量的內(nèi)存大小
- 使用更小的圖片 在涉及給到資源圖片時(shí)稼跳,我們需要特別留意這張圖片是否存在可以壓縮的空間,是否可以使用更小的圖片吃沪。盡量使用更小的圖片不僅可以減少內(nèi)存的使用汤善,還能避免出現(xiàn)大量的InflationException。假設(shè)有一張很大的圖片被XML文件直接引用票彪,很有可能在初始化視圖時(shí)會(huì)因?yàn)閮?nèi)存不足而發(fā)生InflationException红淡,這個(gè)問題的根本原因其實(shí)是發(fā)生了OOM。
- StringBuilder 在有些時(shí)候降铸,代碼中會(huì)需要使用到大量的字符串拼接的操作在旱,這種時(shí)候有必要考慮使用StringBuilder來替代頻繁的“+”。
- 避免在onDraw方法里面執(zhí)行對象的創(chuàng)建 類似onDraw等頻繁調(diào)用的方法推掸,一定需要注意避免在這里做創(chuàng)建對象的操作桶蝎,因?yàn)樗麜?huì)迅速增加內(nèi)存的使用,而且很容易引起頻繁的gc谅畅,甚至是內(nèi)存抖動(dòng)登渣。
- 避免對象的內(nèi)存泄露 android中內(nèi)存泄漏的場景以及解決辦法
ListView 性能優(yōu)化
- 重用converView: 通過復(fù)用converview來減少不必要的view的創(chuàng)建,另外Infalte操作會(huì)把xml文件實(shí)例化成相應(yīng)的View實(shí)例毡泻,屬于IO操作胜茧,是耗時(shí)操作。
- 減少findViewById()操作: 將xml文件中的元素封裝成viewholder靜態(tài)類仇味,通過converview的setTag和getTag方法將view與相應(yīng)的holder對象綁定在一起呻顽,避免不必要的findviewbyid操作
- 避免在 getView 方法中做耗時(shí)的操作: 例如加載本地 Image 需要載入內(nèi)存以及解析 Bitmap ,都是比較耗時(shí)的操作丹墨,如果用戶快速滑動(dòng)listview廊遍,會(huì)因?yàn)間etview邏輯過于復(fù)雜耗時(shí)而造成滑動(dòng)卡頓現(xiàn)象。用戶滑動(dòng)時(shí)候不要加載圖片贩挣,待滑動(dòng)完成再加載喉前,可以使用這個(gè)第三方庫glide
- Item的布局層次結(jié)構(gòu)盡量簡單英染,避免布局太深或者不必要的重繪
- 盡量能保證 Adapter 的 hasStableIds() 返回 true 這樣在 notifyDataSetChanged() 的時(shí)候,如果item內(nèi)容并沒有變化被饿,ListView 將不會(huì)重新繪制這個(gè) View,達(dá)到優(yōu)化的目的
- 在一些場景中搪搏,ScollView內(nèi)會(huì)包含多個(gè)ListView狭握,可以把listview的高度寫死固定下來。 由于ScollView在快速滑動(dòng)過程中需要大量計(jì)算每一個(gè)listview的高度疯溺,阻塞了UI線程導(dǎo)致卡頓現(xiàn)象出現(xiàn)论颅,如果我們每一個(gè)item的高度都是均勻的,可以通過計(jì)算把listview的高度確定下來囱嫩,避免卡頓現(xiàn)象出現(xiàn)
- 使用 RecycleView 代替listview: 每個(gè)item內(nèi)容的變動(dòng)恃疯,listview都需要去調(diào)用notifyDataSetChanged來更新全部的item,太浪費(fèi)性能了墨闲。RecycleView可以實(shí)現(xiàn)當(dāng)個(gè)item的局部刷新今妄,并且引入了增加和刪除的動(dòng)態(tài)效果,在性能上和定制上都有很大的改善
- ListView 中元素避免半透明: 半透明繪制需要大量乘法計(jì)算鸳碧,在滑動(dòng)時(shí)不停重繪會(huì)造成大量的計(jì)算盾鳞,在比較差的機(jī)子上會(huì)比較卡。 在設(shè)計(jì)上能不半透明就不不半透明瞻离。實(shí)在要弄就把在滑動(dòng)的時(shí)候把半透明設(shè)置成不透明腾仅,滑動(dòng)完再重新設(shè)置成半透明。
- 盡量開啟硬件加速: 硬件加速提升巨大套利,避免使用一些不支持的函數(shù)導(dǎo)致含淚關(guān)閉某個(gè)地方的硬件加速推励。當(dāng)然這一條不只是對 ListView。
應(yīng)用常駐后臺(tái)
- Service設(shè)置成START_STICKY kill 后會(huì)被重啟(等待5秒左右)肉迫,重傳Intent验辞,保持與重啟前一樣
- 通過 startForeground將進(jìn)程設(shè)置為前臺(tái)進(jìn)程, 做前臺(tái)服務(wù)昂拂,優(yōu)先級(jí)和前臺(tái)應(yīng)用一個(gè)級(jí)別?受神,除非在系統(tǒng)內(nèi)存非常缺,否則此進(jìn)程不會(huì)被 kill
- 雙進(jìn)程Service: 讓2個(gè)進(jìn)程互相保護(hù)**格侯,其中一個(gè)Service被清理后蚪腐,另外沒被清理的進(jìn)程可以立即重啟進(jìn)程
- QQ黑科技: 在應(yīng)用退到后臺(tái)后,另起一個(gè)只有 1 像素的頁面停留在桌面上耕皮,讓自己保持前臺(tái)狀態(tài)仑濒,保護(hù)自己不被后臺(tái)清理工具殺死
- 在已經(jīng)root的設(shè)備下,修改相應(yīng)的權(quán)限文件,將App偽裝成系統(tǒng)級(jí)的應(yīng)用 Android4.0系列的一個(gè)漏洞朝墩,已經(jīng)確認(rèn)可行
- 用C編寫守護(hù)進(jìn)程(即子進(jìn)程) : Android系統(tǒng)中當(dāng)前進(jìn)程(Process)fork出來的子進(jìn)程醉拓,被系統(tǒng)認(rèn)為是兩個(gè)不同的進(jìn)程。當(dāng)父進(jìn)程被殺死的時(shí)候,子進(jìn)程仍然可以存活亿卤,并不受影響愤兵。鑒于目前提到的在Android->- Service層做雙守護(hù)都會(huì)失敗,我們可以fork出c進(jìn)程排吴,多進(jìn)程守護(hù)秆乳。死循環(huán)在那檢查是否還存在,具體的思路如下(Android5.0以上的版本不可行)
用C編寫守護(hù)進(jìn)程(即子進(jìn)程)钻哩,守護(hù)進(jìn)程做的事情就是循環(huán)檢查目標(biāo)進(jìn)程是否存在屹堰,不存在則啟動(dòng)它。
在NDK環(huán)境中將1中編寫的C代碼編譯打包成可執(zhí)行文件(BUILD_EXECUTABLE)街氢。主進(jìn)程啟動(dòng)時(shí)將守護(hù)進(jìn)程放入私有目錄下扯键,賦予可執(zhí)行權(quán)限,啟動(dòng)它即可珊肃。
Activity間數(shù)據(jù)傳遞 Serializable 和 Parcelable
Serializalbe會(huì)使用反射荣刑,序列化和反序列化過程需要大量I/O操作,Parcelable自已實(shí)現(xiàn)封送和解封(marshalled &unmarshalled)操作不需要用反射近范,數(shù)據(jù)也存放在Native內(nèi)存中嘶摊,效率要快很多。
Parcelable和Parcle這兩者之間的關(guān)系评矩。
Parcelable 接口定義在封送/解封送過程中混合和分解對象的契約叶堆。Parcelable接口的底層是Parcel容器對象。Parcel類是一種最快的序列化/反序列化機(jī)制斥杜,專為Android中的進(jìn)程間通信而設(shè)計(jì)虱颗。該類提供了一些方法來將成員容納到容器中,以及從容器展開成員蔗喂。
注意事項(xiàng)
Intent中的Bundle是在使用Binder機(jī)制進(jìn)行數(shù)據(jù)傳遞的忘渔,能使用的Binder的緩沖區(qū)是有大小限制的(有些手機(jī)是2M),而一個(gè)進(jìn)程默認(rèn)有16個(gè)binder線程缰儿,所以一個(gè)線程能占用的緩沖區(qū)就更小了(以前做過測試畦粮,大約一個(gè)線程可以占用128KB)。所以當(dāng)你看到“The Binder transaction failed because it was too large.”這類TransactionTooLargeException異常時(shí)乖阵,你應(yīng)該知道怎么解決了宣赔。
Android 動(dòng)畫原理
- 逐幀動(dòng)畫(Drawable Animation): 加載一系列Drawable資源來創(chuàng)建動(dòng)畫,簡單來說就是播放一系列的圖片來實(shí)現(xiàn)動(dòng)畫效果瞪浸,可以自定義每張圖片的持續(xù)時(shí)間
- 補(bǔ)間動(dòng)畫(Tween Animation): Tween可以對View對象實(shí)現(xiàn)一系列簡單的動(dòng)畫效果儒将,比如位移,縮放对蒲,旋轉(zhuǎn)钩蚊,透明度等等贡翘。但是它并不會(huì)改變View屬性的值,只是改變了View的繪制的位置砰逻,比如鸣驱,一個(gè)按鈕在動(dòng)畫過后,不在原來的位置蝠咆,但是觸發(fā)點(diǎn)擊事件的仍然是原來的坐標(biāo)丐巫。
- 屬性動(dòng)畫(Property Animation): 動(dòng)畫的對象除了傳統(tǒng)的View對象,還可以是Object對象勺美,動(dòng)畫結(jié)束后,Object對象的屬性值被實(shí)實(shí)在在的改變了
從調(diào)用動(dòng)畫start 開始碑韵,系統(tǒng)每隔 16ms 回調(diào)一次赡茸,也就是每次渲染的時(shí)間。動(dòng)畫根據(jù)初始值和結(jié)束值祝闻,再根據(jù)持續(xù)時(shí)間占卧,每次回調(diào)時(shí)計(jì)算當(dāng)前時(shí)間跟持續(xù)時(shí)間的進(jìn)度,然后根據(jù)進(jìn)度計(jì)算出屬性當(dāng)前時(shí)間點(diǎn)的值联喘,
Android 內(nèi)存泄露 (Java Leak 和 Native Leak)
- Java Leak: 比如 Activity华蜒,service中存在比自己生命周期還長的強(qiáng)引用,當(dāng)Activity 和 service 生命周期結(jié)束后豁遭,但是不能被系統(tǒng) gc 回收叭喜,就造成 內(nèi)存泄露
- Native Leak: 比如當(dāng)用 BitmapFactory decode 出來一個(gè)bitmap 的時(shí)候,由于decode 是調(diào)用的native 代碼蓖谢,當(dāng)bitmap 使用完畢后捂蕴,沒有主動(dòng)調(diào)用 回收方法,那么Java gc 在內(nèi)存回收的時(shí)候只能回收 Java層的內(nèi)存闪幽,而不能回收 native層的內(nèi)存啥辨,于是造成 native 層的內(nèi)存泄露
Java 基礎(chǔ)
多線程
線程池
Executors提供了四種創(chuàng)建ExecutorService的方法
1. Executors.newCachedThreadPool()
創(chuàng)建一個(gè)定長的線程池,每提交一個(gè)任務(wù)就創(chuàng)建一個(gè)線程盯腌,直到達(dá)到池的最大長度溉知,這時(shí)線程池會(huì)保持長度不再變化
2. Executors.newFixedThreadPool()
創(chuàng)建一個(gè)可緩存的線程池,如果當(dāng)前線程池的長度超過了處理的需要時(shí)腕够,它可以靈活的回收空閑的線程级乍,當(dāng)需要增加時(shí),
它可以靈活的添加新的線程燕少,而不會(huì)對池的長度作任何限制
3. Executors.newScheduledThreadPool()
創(chuàng)建一個(gè)定長的線程池卡者,而且支持定時(shí)的以及周期性的任務(wù)執(zhí)行,類似于Timer
4. Executors.newSingleThreadExecutor()
創(chuàng)建一個(gè)單線程化的executor客们,它只創(chuàng)建唯一的worker線程來執(zhí)行任務(wù)
線程同步
synchronized的使用場景
1. 方法同步
public synchronized void method1
鎖住的是該對象,類的其中一個(gè)實(shí)例崇决,當(dāng)該對象(僅僅是這一個(gè)對象)在不同線程中執(zhí)行這個(gè)同步方法時(shí)材诽,線程之間會(huì)形成互斥。達(dá)到同步效果恒傻,但如果不同線程同時(shí)對該類的不同對象執(zhí)行這個(gè)同步方法時(shí)脸侥,則線程之間不會(huì)形成互斥,因?yàn)樗麄儞碛械氖遣煌逆i盈厘。
2. 代碼塊同步
synchronized(this){ //TODO } 描述同①
3. 方法同步
public synchronized static void method3
鎖住的是該類睁枕,當(dāng)所有該類的對象(多個(gè)對象)在不同線程中調(diào)用這個(gè)static同步方法時(shí),線程之間會(huì)形成互斥沸手,達(dá)到同步效果外遇。
4. 代碼塊同步
synchronized(Test.class){ //TODO} 同③
5. 代碼塊同步
synchronized(o) {}
這里面的o可以是一個(gè)任何Object對象或數(shù)組,并不一定是它本身對象或者類契吉,誰擁有o這個(gè)鎖跳仿,誰就能夠操作該塊程序代碼。
Lock 接口:
Lock捐晶,鎖對象菲语。在Java中鎖是用來控制多個(gè)線程訪問共享資源的方式,一般來說惑灵,一個(gè)鎖能夠防止多個(gè)線程同時(shí)訪問共享資源(但有的鎖可以允許多個(gè)線程并發(fā)訪問共享資源山上,比如讀寫鎖,后面我們會(huì)分析)英支。在Lock接口出現(xiàn)之前佩憾,Java程序是靠synchronized關(guān)鍵字(后面分析)實(shí)現(xiàn)鎖功能的,而JAVA SE5.0之后并發(fā)包中新增了Lock接口用來實(shí)現(xiàn)鎖的功能干花,它提供了與synchronized關(guān)鍵字類似的同步功能鸯屿,只是在使用時(shí)需要顯式地獲取和釋放鎖,缺點(diǎn)就是缺少像synchronized那樣隱式獲取釋放鎖的便捷性把敢,但是卻擁有了鎖獲取與釋放的可操作性寄摆,可中斷的獲取鎖以及超時(shí)獲取鎖等多種synchronized關(guān)鍵字所不具備的同步特性。
CPU在調(diào)度線程的時(shí)候是在等待隊(duì)列里隨機(jī)挑選一個(gè)線程修赞,由于這種隨機(jī)性所以是無法保證線程先到先得的(synchronized控制的鎖就是這種非公平鎖)婶恼。但這樣就會(huì)產(chǎn)生饑餓現(xiàn)象,即有些線程(優(yōu)先級(jí)較低的線程)可能永遠(yuǎn)也無法獲取CPU的執(zhí)行權(quán)柏副,優(yōu)先級(jí)高的線程會(huì)不斷的強(qiáng)制它的資源勾邦。那么如何解決饑餓問題呢,這就需要公平鎖了割择。公平鎖可以保證線程按照時(shí)間的先后順序執(zhí)行眷篇,避免饑餓現(xiàn)象的產(chǎn)生。但公平鎖的效率比較低荔泳,因?yàn)橐獙?shí)現(xiàn)順序執(zhí)行蕉饼,需要維護(hù)一個(gè)有序隊(duì)列虐杯。
ReentrantLock便是一種公平鎖,通過在構(gòu)造方法中傳入true就是公平鎖昧港,傳入false擎椰,就是非公平鎖。
synchronized和ReentrantLock的比較
- Lock是一個(gè)接口创肥,而synchronized是Java中的關(guān)鍵字达舒,synchronized是內(nèi)置的語言實(shí)現(xiàn);
- synchronized在發(fā)生異常時(shí)叹侄,會(huì)自動(dòng)釋放線程占有的鎖巩搏,因此不會(huì)導(dǎo)致死鎖現(xiàn)象發(fā)生;而Lock在發(fā)生異常時(shí)趾代,如果沒有主動(dòng)通過unLock()去釋放鎖塔猾,則很可能造成死鎖現(xiàn)象,因此使用Lock時(shí)需要在finally塊中釋放鎖稽坤;
- Lock可以讓等待鎖的線程響應(yīng)中斷,而synchronized卻不行糯俗,使用synchronized時(shí)尿褪,等待的線程會(huì)一直等待下去,不能夠響應(yīng)中斷得湘;
- 通過Lock可以知道有沒有成功獲取鎖杖玲,而synchronized卻無法辦到。
- Lock可以提高多個(gè)線程進(jìn)行讀操作的效率淘正。
Runnable接口和Callable接口的區(qū)別
Runnable接口中的run()方法的返回值是void摆马,它做的事情只是純粹地去執(zhí)行run()方法中的代碼而已;Callable接口中的call()方法是有返回值的鸿吆,是一個(gè)泛型囤采,和Future、FutureTask配合可以用來獲取異步執(zhí)行的結(jié)果惩淳。
這其實(shí)是很有用的一個(gè)特性蕉毯,因?yàn)槎嗑€程相比單線程更難、更復(fù)雜的一個(gè)重要原因就是因?yàn)槎嗑€程充滿著未知性思犁,某條線程是否執(zhí)行了代虾?某條線程執(zhí)行了多久?某條線程執(zhí)行的時(shí)候我們期望的數(shù)據(jù)是否已經(jīng)賦值完畢激蹲?無法得知棉磨,我們能做的只是等待這條多線程的任務(wù)執(zhí)行完畢而已。而Callable+Future/FutureTask卻可以獲取多線程運(yùn)行的結(jié)果学辱,可以在等待時(shí)間太長沒獲取到需要的數(shù)據(jù)的情況下取消該線程的任務(wù)乘瓤,真的是非常有用环形。
volatile關(guān)鍵字的作用
一個(gè)非常重要的問題,是每個(gè)學(xué)習(xí)馅扣、應(yīng)用多線程的Java程序員都必須掌握的斟赚。理解volatile關(guān)鍵字的作用的前提是要理解Java內(nèi)存模型,這里就不講Java內(nèi)存模型了差油,可以參見第31點(diǎn)拗军,volatile關(guān)鍵字的作用主要有兩個(gè):
- 多線程主要圍繞可見性和原子性兩個(gè)特性而展開,使用volatile關(guān)鍵字修飾的變量蓄喇,保證了其在多線程之間的可見性发侵,即每次讀取到volatile變量,一定是最新的數(shù)據(jù)
- 代碼底層執(zhí)行不像我們看到的高級(jí)語言—-Java程序這么簡單妆偏,它的執(zhí)行是Java代碼–>字節(jié)碼–>根據(jù)字節(jié)碼執(zhí)行對應(yīng)的C/C++代碼–>C/C++代碼被編譯成匯編語言–>和硬件電路交互刃鳄,現(xiàn)實(shí)中,為了獲取更好的性能JVM可能會(huì)對指令進(jìn)行重排序钱骂,多線程下可能會(huì)出現(xiàn)一些意想不到的問題叔锐。使用volatile則會(huì)對禁止語義重排序,當(dāng)然這也一定程度上降低了代碼執(zhí)行效率
從實(shí)踐角度而言见秽,volatile的一個(gè)重要作用就是和CAS結(jié)合愉烙,保證了原子性,詳細(xì)的可以參見java.util.concurrent.atomic包下的類解取,比如AtomicInteger步责。
線程安全
Java中標(biāo)注自己是線程安全的類,實(shí)際上絕大多數(shù)都不是線程安全的禀苦,不過絕對線程安全的類蔓肯,Java中也有,比方說CopyOnWriteArrayList振乏、CopyOnWriteArraySet
相對線程安全也就是我們通常意義上所說的線程安全蔗包,像Vector這種,add慧邮、remove方法都是原子操作气忠,不會(huì)被打斷,但也僅限于此赋咽,如果有個(gè)線程在遍歷某個(gè)Vector旧噪、有個(gè)線程同時(shí)在add這個(gè)Vector,99%的情況下都會(huì)出現(xiàn)ConcurrentModificationException脓匿,也就是fail-fast機(jī)制淘钟。
算法篇
冒泡排序
原理:臨近的數(shù)字兩兩進(jìn)行比較,按照從小到大或者從大到小的順序進(jìn)行交換,這樣一趟過去后,最大或最小的數(shù)字被交換到了最后一位,然后再從頭開始進(jìn)行兩兩比較交換,直到倒數(shù)第二位時(shí)結(jié)束
void bubbleSort(int[] unsorted)
{
for (int i = 0; i < unsorted.Length; i++)
{
for (int j = i; j < unsorted.Length; j++)
{
if (unsorted[i] > unsorted[j])
{
int temp = unsorted[i];
unsorted[i] = unsorted[j];
unsorted[j] = temp;
}
}
}
}
鏈表是否有環(huán)
通過快慢指針
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class ChkLoop {
public int chkLoop(ListNode head, int adjust) {
// write code here
if(head==null||head.next==null)return -1;
ListNode fast=head,slow=head;
do{
slow=slow.next;
fast=fast.next.next;
}while(slow!=fast&&fast.next!=null&&fast.next.next!=null);
if(slow!=fast)return -1;
slow=head;
while(slow!=fast){
fast=fast.next;
slow=slow.next;
}
return slow.val;
}
}
不用遞歸計(jì)算斐波那契數(shù)列fib
可以通過循環(huán)來做
public int fib(int index) {
if (index <= 2)
return 1;
int f1 = 1;// 前前位
int f2 = 1;// 前一位
int fn = 0;
for (int i = 0; i < index - 2; i++) {
//這里是換位操作
fn = f1 + f2;
f1 = f2;
f2 = fn;
}
return fn;
}