Java知識(shí)復(fù)習(xí)解答
1.synchronize 和 volitale的區(qū)別;什么是可見性和原子性
可見性:當(dāng)一個(gè)線程修改了線程共享變量的值,其他線程能夠立即得知這個(gè)修改
原子性:一系列的操作要么全部執(zhí)行完,要么都不執(zhí)行
有序性:如果在本線程內(nèi)觀察,所有操作都是有序的;如果在一個(gè)線程中觀察另一個(gè)線程,所有操作都是無序的。前半句是指“線程內(nèi)表現(xiàn)為串行語義”,后半句是指“指令重排序”現(xiàn)象和“工作內(nèi)存中主內(nèi)存同步延遲”現(xiàn)象。
synchronized具有原子性,可見性,有序性,同一時(shí)刻只能有一個(gè)線程進(jìn)行獲??;會(huì)造成線程阻塞;標(biāo)記的變量會(huì)被編譯器優(yōu)化;可以使用在變量,方法,類級(jí)別中
volatile具有可見性,有序性,不能保證原子性,(需要保證原子性,1:運(yùn)算結(jié)果不依賴變量的當(dāng)前值,或者只有一個(gè)線程修改變量的值;2:該變量沒有包含在具有其他變量的不變式中);不會(huì)造成線程阻塞;標(biāo)記的變量不會(huì)被編譯器優(yōu)化(防止指令重排);只能使用在變量級(jí)別
2.Java的集合類
Java的集合類是放在java.util包下;集合類存放的是對(duì)象的引用,而非對(duì)象的本身;集合類主要分為Set(集),List(列表),Map(映射)。
List和Set是Collection(接口)的子類。
Set:不能包含重復(fù)的元素,沒有順序
List:
-
ArrayList:是List的子類,底層是動(dòng)態(tài)擴(kuò)容的數(shù)組結(jié)構(gòu)??梢源娣胖貜?fù)的元素(包括null);有序,按照元素的添加順序,多用于查詢多增刪操作少的情況,ArrayList是線程異步的,是不安全。
擴(kuò)容機(jī)制:每次擴(kuò)容的大小是原來的1.5倍;擴(kuò)容的過程其實(shí)就是一個(gè)將原來元素拷貝到一個(gè)擴(kuò)容后數(shù)組大小的長(zhǎng)度新數(shù)組中,所以ArrayList的擴(kuò)容其實(shí)是相對(duì)來說是比較消耗性能的。經(jīng)常出現(xiàn)的一個(gè)異常:ConcurrentModificationException,在foreach循環(huán)的時(shí)候,進(jìn)行了刪除操作,集合的長(zhǎng)度產(chǎn)生的變化,
LinkedList:LinkedList是一種可以在任何位置進(jìn)行高效地插入和刪除操作的有序序列,多用于增刪操作多的情況下。底層實(shí)現(xiàn)的數(shù)據(jù)結(jié)構(gòu)是雙向鏈表。也是不安全的
Map是util包下另一個(gè)集合類,是以key-value形式,鍵不能重復(fù),值可以重復(fù)。對(duì)Map集合遍歷時(shí)先得到鍵的set集合,再對(duì)set集合進(jìn)行遍歷,得到相應(yīng)的值。
HashMap:
- 存儲(chǔ)數(shù)據(jù)是根據(jù)鍵值對(duì)存儲(chǔ)數(shù)據(jù)的,并且存儲(chǔ)多個(gè)數(shù)據(jù)時(shí),數(shù)據(jù)的鍵不能相同,如果相同該鍵之前對(duì)應(yīng)的值將被覆蓋。
- HashMap最多只允許一條存儲(chǔ)數(shù)據(jù)的鍵為null,可允許多條數(shù)據(jù)的值為null。
- HashMap存儲(chǔ)數(shù)據(jù)的順序是不確定的,并且可能會(huì)因?yàn)閿U(kuò)容導(dǎo)致元素存儲(chǔ)位置改變。因此遍歷順序是不確定的。
- HashMap是線程不安全的,使用ConcurrentHashMap具有線程安全。
底層存儲(chǔ)結(jié)構(gòu):
JDK1.7之前的存儲(chǔ)結(jié)構(gòu):
拉鏈法,專業(yè)點(diǎn)就叫鏈地址法。就是數(shù)組加鏈表的結(jié)合。每一個(gè)數(shù)組元素上存儲(chǔ)的都是一個(gè)鏈表。新添加進(jìn)來的元素總是放在數(shù)組對(duì)應(yīng)的角標(biāo)位置,而原來處于該角標(biāo)的位置的節(jié)點(diǎn)作為next節(jié)點(diǎn)放到新節(jié)點(diǎn)的后邊。
JDK1.8中的數(shù)據(jù)結(jié)構(gòu):
如果單單是數(shù)組加鏈表的話,當(dāng)處理hash值沖突較多的情況下,鏈表的長(zhǎng)度會(huì)越來越長(zhǎng),查找的效率會(huì)越來越低了。所以在1.8中當(dāng)新增節(jié)點(diǎn)導(dǎo)致鏈表的長(zhǎng)度超過8的時(shí)候,就會(huì)在添加元素的同事將單鏈表轉(zhuǎn)化為紅黑樹。紅黑樹是一種易于增刪改查的二叉樹,這樣HashMap中的元素操作起來就會(huì)更高效。
3. Java中的引用
- StrongReference(強(qiáng)引用):從不回收,對(duì)象一直存在,垃圾回收器絕對(duì)不會(huì)回收它;當(dāng)JVM停止的時(shí)候才被終止
- SoftReference(軟引用):可以和引用隊(duì)列(ReferenceQueue)聯(lián)合使用;內(nèi)存足夠,不會(huì)回收,內(nèi)存不夠就會(huì)回收;
- WeakReference(弱引用):可以和引用隊(duì)列(ReferenceQueue)聯(lián)合使用,當(dāng)內(nèi)存不足時(shí),觸發(fā)GC后被終止。多用于內(nèi)存泄漏的解決;可以通過手動(dòng)GC進(jìn)行清除。
- PhantomReference(虛引用):必須和引用隊(duì)列(ReferenceQueue)聯(lián)合使用,隨時(shí)會(huì)被回收,觸發(fā)GC后被終止
4. Java GC回收機(jī)制
GC機(jī)制:是Java虛擬機(jī)垃圾回收器提供的一種用于在空閑時(shí)間不定時(shí)回收無任何對(duì)象引用的對(duì)象占據(jù)的內(nèi)存空間的一種機(jī)制。
JVM把內(nèi)存劃分成了下面幾個(gè)區(qū)域:
-
程序計(jì)數(shù)器
每一個(gè)線程都有它自己的程序計(jì)數(shù)器,并且任何時(shí)間一個(gè)線程都只有一個(gè)方法在執(zhí)行,也就是所謂的當(dāng)前方法。會(huì)存儲(chǔ)當(dāng)前線程正在執(zhí)行的Java方法的JVM指令地址;如果是本地方法,則是未指定值(undefined)。
-
方法區(qū)
用于存儲(chǔ)所謂的元數(shù)據(jù),例如類的信息(名稱,修飾符等)、運(yùn)行時(shí)常量池、類中的字段和方法等。方法區(qū)GC,條件比較苛刻
運(yùn)行時(shí)常量池:常量池可以存放各種常量信息,不管是編譯器生成的各種字面量,還是需要在運(yùn)行時(shí)決定的符號(hào)引用。
-
堆區(qū)
是Java內(nèi)存管理的核心區(qū)域,用來放置Java對(duì)象實(shí)例,幾乎所有創(chuàng)建的Java對(duì)象實(shí)例都是被直接分配在堆上的。所以堆區(qū)是GC最頻繁的
-
Java虛擬機(jī)棧
每個(gè)線程在創(chuàng)建時(shí)都會(huì)創(chuàng)建一個(gè)虛擬機(jī)棧,線程私有,生命周期和線程一樣,每一個(gè)方法被調(diào)用時(shí)產(chǎn)生一個(gè)棧幀。JVM直接對(duì)Java棧的操作只有兩個(gè),就是對(duì)棧幀的壓棧和出棧。
棧幀中存儲(chǔ)著局部變量表、操作數(shù)、動(dòng)態(tài)鏈接、方法出口。
-
本地方法棧
和Java虛擬機(jī)棧非常相似,支持對(duì)本地方法的調(diào)用,也是每個(gè)線程都會(huì)創(chuàng)建一個(gè)。
方法區(qū)和堆歸所有線程共享
在上面介紹的五個(gè)內(nèi)存區(qū)域中,有3個(gè)是不需要進(jìn)行垃圾回收的:本地方法棧、程序計(jì)數(shù)器、虛擬機(jī)棧。因?yàn)樗麄兊纳芷谑呛途€程同步的,隨著線程的銷毀,他們占用的內(nèi)存會(huì)自動(dòng)釋放。所以,只有方法區(qū)和堆區(qū)需要進(jìn)行垃圾回收,回收的對(duì)象就是那些不存在任何引用的對(duì)象。
GC算法:
經(jīng)典的引用計(jì)數(shù)算法,很難處理循環(huán)引用關(guān)系,所以Java并沒有采用這種方法。而是采用追蹤性垃圾收集,就是根搜索算法。
常見的垃圾收集算法:
- 復(fù)制算法:高效,但是需要提前預(yù)留空間,有一定浪費(fèi)。
- 標(biāo)記-清除:先進(jìn)行標(biāo)記,標(biāo)記處所有要回收的對(duì)象,然后進(jìn)行清除。有碎片化的問題,不適合特別大的堆。
- 標(biāo)記-整理:類似清除,但是為了避免內(nèi)存碎片化,在清理過程中將對(duì)象移動(dòng),以確保移動(dòng)后的對(duì)象占用連續(xù)的內(nèi)存空間。
5.Java的內(nèi)存模型
Java內(nèi)存模型(Java Memory Model ,JMM)就是一種符合內(nèi)存模型規(guī)范的,屏蔽了各種硬件和操作系統(tǒng)的訪問差異的,保證了Java程序在各種平臺(tái)下對(duì)內(nèi)存的訪問都能保證效果一致的機(jī)制及規(guī)范。
6.多進(jìn)程和多線程
進(jìn)程是資源分配的最小單位,線程是CPU調(diào)度的最小單位
7. 線程的休眠方式,sleep和wait哪個(gè)會(huì)釋放鎖?
sleep是Thread類的方法,不會(huì)釋放對(duì)象鎖。
wait是Objec類的方法,線程會(huì)釋放對(duì)象鎖,可以用notify()或者notifyAll()喚醒。
8. String、StringBuffer、StringBuilder的區(qū)別
都是被final修飾的,都不允許被繼承。
String長(zhǎng)度固定,StringBuffer和StringBuilder長(zhǎng)度是可以改變的;StringBuffer是線程安全的,StringBuilder不是線程安全的。StringBuffer多用于多線程,操作大量數(shù)據(jù),StringBuilder用于單線程操作大量數(shù)據(jù),效率優(yōu)于StringBuffer;拼接字符串不建議使用+,因?yàn)閮?nèi)部也是額外創(chuàng)建StringBuffer來完成的。
9. 抽象類和接口有什么區(qū)別?
共同點(diǎn):是上層的抽象層。 都不能被實(shí)例化。 都能包含抽象的方法,這些抽象的方法用于描述類具備的功能,但是不會(huì)提供具體的實(shí)現(xiàn)。
區(qū)別:抽象類里面可以寫非抽象方法,接口中只能有抽象方法;類只能繼承一個(gè)類,可以是抽象類。類可以實(shí)現(xiàn)多個(gè)接口。抽象類抽象方法可以用所有修飾符修飾,接口里面的方法都是public,在JDK1.8允許一個(gè)靜態(tài)方法和多個(gè)Default方法。抽象類可以有構(gòu)造方法,接口不能有構(gòu)造器。
10.單例模式,雙判空懶漢單例模式,為什么變量用volitale修飾,new不是原子操作
用volitale修飾,是禁止指令重排。new不是原子操作,有多個(gè)步驟。