Android復(fù)習(xí)資料——Java知識(shí)點(diǎn)匯總(寶藏干貨)

jvm
jvm工作流程

運(yùn)行時(shí)數(shù)據(jù)區(qū)(Runtime Data Area)


方法指令

類加載器


static
static關(guān)鍵字修飾的方法或者變量不需要依賴于對(duì)象來進(jìn)行訪問,只要類被加載了,就可以通過類名去進(jìn)行訪問。
靜態(tài)變量被所有的對(duì)象所共享,在內(nèi)存中只有一個(gè)副本,它當(dāng)且僅當(dāng)在類初次加載時(shí)會(huì)被初始化。
能通過this訪問靜態(tài)成員變量嗎? 所有的靜態(tài)方法和靜態(tài)變量都可以通過對(duì)象訪問(只要訪問權(quán)限足夠)。
static是不允許用來修飾局部變量
final
可以聲明成員變量、方法、類以及本地變量
final成員變量必須在聲明的時(shí)候初始化或者在構(gòu)造器中初始化,否則就會(huì)報(bào)編譯錯(cuò)誤
final變量是只讀的
final申明的方法不可以被子類的方法重寫
final類通常功能是完整的,不能被繼承
final變量可以安全的在多線程環(huán)境下進(jìn)行共享,而不需要額外的同步開銷
final關(guān)鍵字提高了性能,JVM和Java應(yīng)用都會(huì)緩存final變量,會(huì)對(duì)方法、變量及類進(jìn)行優(yōu)化
方法的內(nèi)部類訪問方法中的局部變量,但必須用final修飾才能訪問
String、StringBuffer、StringBuilder
String是final類,不能被繼承。對(duì)于已經(jīng)存在的Stirng對(duì)象,修改它的值,就是重新創(chuàng)建一個(gè)對(duì)象
StringBuffer是一個(gè)類似于String的字符串緩沖區(qū),使用append()方法修改Stringbuffer的值,使用toString()方法轉(zhuǎn)換為字符串,是線程安全的
StringBuilder用來替代于StringBuffer,StringBuilder是非線程安全的,速度更快
異常處理
Exception、Error是Throwable類的子類
Error類對(duì)象由Java虛擬機(jī)生成并拋出,不可捕捉
不管有沒有異常,finally中的代碼都會(huì)執(zhí)行
當(dāng)try、catch中有return時(shí),finally中的代碼依然會(huì)繼續(xù)執(zhí)行

內(nèi)部類
內(nèi)部類提供了更好的封裝,可以把內(nèi)部類隱藏在外部類之內(nèi),不允許同一個(gè)包中的其他類訪問該類。
內(nèi)部類的方法可以直接訪問外部類的所有數(shù)據(jù),包括私有的數(shù)據(jù)。
多態(tài)
父類的引用可以指向子類的對(duì)象
創(chuàng)建子類對(duì)象時(shí),調(diào)用的方法為子類重寫的方法或者繼承的方法
如果我們?cè)谧宇愔芯帉懸粋€(gè)獨(dú)有的方法,此時(shí)就不能通過父類的引用創(chuàng)建的子類對(duì)象來調(diào)用該方法
抽象和接口
抽象類不能有對(duì)象(不能用new此關(guān)鍵字來創(chuàng)建抽象類的對(duì)象)
抽象類中的抽象方法必須在子類中被重寫
接口中的所有屬性默認(rèn)為:public static final ****;
接口中的所有方法默認(rèn)為:public abstract ****;
集合

List接口存儲(chǔ)一組不唯一,有序(插入順序)的對(duì)象, Set接口存儲(chǔ)一組唯一,無序的對(duì)象。
HashMap是非synchronized的,性能更好,HashMap可以接受為null的key和value,而Hashtable是線程安全的,比HashMap要慢,不接受null
反射
單例
餓漢式
雙重檢查模式
靜態(tài)內(nèi)部類模式
靜態(tài)內(nèi)部類的原理是:
當(dāng)SingleTon第一次被加載時(shí),并不需要去加載SingleTonHoler,只有當(dāng)getInstance()方法第一次被調(diào)用時(shí),才會(huì)去初始化INSTANCE,這種方法不僅能確保線程安全,也能保證單例的唯一性,同時(shí)也延遲了單例的實(shí)例化。getInstance()方法并沒有多次去new對(duì)象,取的都是同一個(gè)INSTANCE對(duì)象。
虛擬機(jī)會(huì)保證一個(gè)類的<clinit>()
方法在多線程環(huán)境中被正確地加鎖、同步,如果多個(gè)線程同時(shí)去初始化一個(gè)類,那么只會(huì)有一個(gè)線程去執(zhí)行這個(gè)類的<clinit>()
方法,其他線程都需要阻塞等待,直到活動(dòng)線程執(zhí)行<clinit>()
方法完畢
缺點(diǎn)在于無法傳遞參數(shù),如Context等
線程

valatile
當(dāng)把變量聲明為volatile類型后,編譯器與運(yùn)行時(shí)都會(huì)注意到這個(gè)變量是共享的,因此不會(huì)將該變量上的操作與其他內(nèi)存操作一起重排序。volatile變量不會(huì)被緩存在寄存器或者對(duì)其他處理器不可見的地方,JVM 保證了每次讀變量都從內(nèi)存中讀,跳過 CPU cache 這一步,因此在讀取volatile類型的變量時(shí)總會(huì)返回最新寫入的值。

當(dāng)一個(gè)變量定義為 volatile 之后,將具備以下特性:
保證此變量對(duì)所有的線程的可見性,不能保證它具有原子性(可見性,是指線程之間的可見性,一個(gè)線程修改的狀態(tài)對(duì)另一個(gè)線程是可見的)
禁止指令重排序優(yōu)化
volatile 的讀性能消耗與普通變量幾乎相同,但是寫操作稍慢,因?yàn)樗枰诒镜卮a中插入許多內(nèi)存屏障指令來保證處理器不發(fā)生亂序執(zhí)行
AtomicInteger 中主要實(shí)現(xiàn)了整型的原子操作,防止并發(fā)情況下出現(xiàn)異常結(jié)果,其內(nèi)部主要依靠JDK 中的unsafe 類操作內(nèi)存中的數(shù)據(jù)來實(shí)現(xiàn)的。volatile 修飾符保證了value在內(nèi)存中其他線程可以看到其值得改變。CAS操作保證了AtomicInteger 可以安全的修改value 的值。
HashMap

HashMap的工作原理
HashMap基于hashing原理,我們通過put()和get()方法儲(chǔ)存和獲取對(duì)象。當(dāng)我們將鍵值對(duì)傳遞給put()方法時(shí),它調(diào)用鍵對(duì)象的hashCode()方法來計(jì)算hashcode,讓后找到bucket位置來儲(chǔ)存Entry對(duì)象。當(dāng)兩個(gè)對(duì)象的hashcode相同時(shí),它們的bucket位置相同,‘碰撞’會(huì)發(fā)生。因?yàn)镠ashMap使用鏈表存儲(chǔ)對(duì)象,這個(gè)Entry會(huì)存儲(chǔ)在鏈表中,當(dāng)獲取對(duì)象時(shí),通過鍵對(duì)象的equals()方法找到正確的鍵值對(duì),然后返回值對(duì)象。
如果HashMap的大小超過了負(fù)載因子(load factor)定義的容量,怎么辦?
默認(rèn)的負(fù)載因子大小為0.75,也就是說,當(dāng)一個(gè)map填滿了75%的bucket時(shí)候,和其它集合類(如ArrayList等)一樣,將會(huì)創(chuàng)建原來HashMap大小的兩倍的bucket數(shù)組,來重新調(diào)整map的大小,并將原來的對(duì)象放入新的bucket數(shù)組中。這個(gè)過程叫作rehashing,因?yàn)樗{(diào)用hash方法找到新的bucket位置。
為什么String, Interger這樣的wrapper類適合作為鍵?
因?yàn)镾tring是不可變的,也是final的,而且已經(jīng)重寫了equals()和hashCode()方法了。其他的wrapper類也有這個(gè)特點(diǎn)。不可變性是必要的,因?yàn)闉榱艘?jì)算hashCode(),就要防止鍵值改變,如果鍵值在放入時(shí)和獲取時(shí)返回不同的hashcode的話,那么就不能從HashMap中找到你想要的對(duì)象。不可變性還有其他的優(yōu)點(diǎn)如線程安全。如果你可以僅僅通過將某個(gè)field聲明成final就能保證hashCode是不變的,那么請(qǐng)這么做吧。因?yàn)楂@取對(duì)象的時(shí)候要用到equals()和hashCode()方法,那么鍵對(duì)象正確的重寫這兩個(gè)方法是非常重要的。如果兩個(gè)不相等的對(duì)象返回不同的hashcode的話,那么碰撞的幾率就會(huì)小些,這樣就能提高HashMap的性能。
synchronized
當(dāng)它用來修飾一個(gè)方法或者一個(gè)代碼塊的時(shí)候,能夠保證在同一時(shí)刻最多只有一個(gè)線程執(zhí)行該段代碼。
在 Java 中,每個(gè)對(duì)象都會(huì)有一個(gè) monitor 對(duì)象,這個(gè)對(duì)象其實(shí)就是 Java 對(duì)象的鎖,通常會(huì)被稱為“內(nèi)置鎖”或“對(duì)象鎖”。類的對(duì)象可以有多個(gè),所以每個(gè)對(duì)象有其獨(dú)立的對(duì)象鎖,互不干擾。針對(duì)每個(gè)類也有一個(gè)鎖,可以稱為“類鎖”,類鎖實(shí)際上是通過對(duì)象鎖實(shí)現(xiàn)的,即類的 Class 對(duì)象鎖。每個(gè)類只有一個(gè) Class 對(duì)象,所以每個(gè)類只有一個(gè)類鎖。
Monitor是線程私有的數(shù)據(jù)結(jié)構(gòu),每一個(gè)線程都有一個(gè)可用monitor record列表,同時(shí)還有一個(gè)全局的可用列表。每一個(gè)被鎖住的對(duì)象都會(huì)和一個(gè)monitor關(guān)聯(lián),同時(shí)monitor中有一個(gè)Owner字段存放擁有該鎖的線程的唯一標(biāo)識(shí),表示該鎖被這個(gè)線程占用。Monitor是依賴于底層的操作系統(tǒng)的Mutex Lock(互斥鎖)來實(shí)現(xiàn)的線程同步。
根據(jù)獲取的鎖分類
獲取對(duì)象鎖
synchronized(this|object) {}
修飾非靜態(tài)方法
獲取類鎖
synchronized(類.class) {}
修飾靜態(tài)方法
原理
同步代碼塊:
monitorenter和monitorexit指令實(shí)現(xiàn)的
同步方法
方法修飾符上的ACC_SYNCHRONIZED實(shí)現(xiàn)
Lock

悲觀鎖、樂觀鎖
悲觀鎖認(rèn)為自己在使用數(shù)據(jù)的時(shí)候一定有別的線程來修改數(shù)據(jù),因此在獲取數(shù)據(jù)的時(shí)候會(huì)先加鎖,確保數(shù)據(jù)不會(huì)被別的線程修改。Java中,synchronized關(guān)鍵字和Lock的實(shí)現(xiàn)類都是悲觀鎖。悲觀鎖適合寫操作多的場(chǎng)景,先加鎖可以保證寫操作時(shí)數(shù)據(jù)正確。
而樂觀鎖認(rèn)為自己在使用數(shù)據(jù)時(shí)不會(huì)有別的線程修改數(shù)據(jù),所以不會(huì)添加鎖,只是在更新數(shù)據(jù)的時(shí)候去判斷之前有沒有別的線程更新了這個(gè)數(shù)據(jù)。如果這個(gè)數(shù)據(jù)沒有被更新,當(dāng)前線程將自己修改的數(shù)據(jù)成功寫入。如果數(shù)據(jù)已經(jīng)被其他線程更新,則根據(jù)不同的實(shí)現(xiàn)方式執(zhí)行不同的操作(例如報(bào)錯(cuò)或者自動(dòng)重試)。樂觀鎖在Java中是通過使用無鎖編程來實(shí)現(xiàn),最常采用的是CAS算法,Java原子類中的遞增操作就通過CAS自旋實(shí)現(xiàn)。樂觀鎖適合讀操作多的場(chǎng)景,不加鎖的特點(diǎn)能夠使其讀操作的性能大幅提升。
自旋鎖、適應(yīng)性自旋鎖
阻塞或喚醒一個(gè)Java線程需要操作系統(tǒng)切換CPU狀態(tài)來完成,這種狀態(tài)轉(zhuǎn)換需要耗費(fèi)處理器時(shí)間。如果同步代碼塊中的內(nèi)容過于簡(jiǎn)單,狀態(tài)轉(zhuǎn)換消耗的時(shí)間有可能比用戶代碼執(zhí)行的時(shí)間還要長(zhǎng)。
在許多場(chǎng)景中,同步資源的鎖定時(shí)間很短,為了這一小段時(shí)間去切換線程,線程掛起和恢復(fù)現(xiàn)場(chǎng)的花費(fèi)可能會(huì)讓系統(tǒng)得不償失。如果物理機(jī)器有多個(gè)處理器,能夠讓兩個(gè)或以上的線程同時(shí)并行執(zhí)行,我們就可以讓后面那個(gè)請(qǐng)求鎖的線程不放棄CPU的執(zhí)行時(shí)間,看看持有鎖的線程是否很快就會(huì)釋放鎖。
而為了讓當(dāng)前線程“稍等一下”,我們需讓當(dāng)前線程進(jìn)行自旋,如果在自旋完成后前面鎖定同步資源的線程已經(jīng)釋放了鎖,那么當(dāng)前線程就可以不必阻塞而是直接獲取同步資源,從而避免切換線程的開銷。這就是自旋鎖。
自旋鎖本身是有缺點(diǎn)的,它不能代替阻塞。自旋等待雖然避免了線程切換的開銷,但它要占用處理器時(shí)間。如果鎖被占用的時(shí)間很短,自旋等待的效果就會(huì)非常好。反之,如果鎖被占用的時(shí)間很長(zhǎng),那么自旋的線程只會(huì)白浪費(fèi)處理器資源。所以,自旋等待的時(shí)間必須要有一定的限度,如果自旋超過了限定次數(shù)(默認(rèn)是10次,可以使用-XX:PreBlockSpin來更改)沒有成功獲得鎖,就應(yīng)當(dāng)掛起線程。
自旋鎖的實(shí)現(xiàn)原理同樣也是CAS,AtomicInteger中調(diào)用unsafe進(jìn)行自增操作的源碼中的do-while循環(huán)就是一個(gè)自旋操作,如果修改數(shù)值失敗則通過循環(huán)來執(zhí)行自旋,直至修改成功。
死鎖
當(dāng)前線程擁有其他線程需要的資源,當(dāng)前線程等待其他線程已擁有的資源,都不放棄自己擁有的資源。
本次的分享就到這啦,喜歡的話可以點(diǎn)個(gè)贊??或關(guān)注,
這里的話我也將我之前在學(xué)習(xí)過程中用到的一些學(xué)習(xí)資料整理成了文檔,以及我自身之前的一個(gè)面試文案和知識(shí)點(diǎn)補(bǔ)充,有需要的朋友可以評(píng)論區(qū)留言,學(xué)習(xí)資料免費(fèi)下載,這里也祝愿大家最終都能夠?qū)W有所成,早日找到滿意的工作!
適合初學(xué)者學(xué)習(xí)的2021最新Java學(xué)習(xí)視頻,書籍,面試題,PDF文檔,都是經(jīng)典干貨!