面試題

最近在準備找工作,所以自己總結(jié)了一下面試題,有些來自于網(wǎng)上的,自己做了下整理。
1、JDK 和 JRE 有什么區(qū)別?

JDK:Java Development Kit 的簡稱,java 開發(fā)工具包,提供了 java 的開發(fā)環(huán)境和運行環(huán)境。

JRE:Java Runtime Environment 的簡稱,java 運行環(huán)境,為 java 的運行提供了所需環(huán)境。

具體來說 JDK 其實包含了 JRE,同時還包含了編譯 java 源碼的編譯器 javac,還包含了很多 java 程序調(diào)試和分析的工具。簡單來說:如果你需要運行 java 程序,只需安裝 JRE 就可以了,如果你需要編寫 java 程序,需要安裝 JDK。

2、包裝類和基本數(shù)據(jù)類型區(qū)別?

聲明方式不同:基本類型不使用new關(guān)鍵字,而包裝類型需要使用new關(guān)鍵字來在堆中分配存儲空間;

存儲方式及位置不同:基本類型是直接將變量值存儲在<u>棧中</u>,而包裝類型是將對象放在堆中,然后通過引用來使用;

初始值不同:基本類型的初始值如int為0,boolean為false,而包裝類型的初始值為null;

使用方式不同:基本類型直接賦值直接使用就好,而包裝類型在集合如Collection、Map時會使用到。

3、Java 中堆和棧有什么區(qū)別?

JVM 中堆和棧屬于不同的內(nèi)存區(qū)域,使用目的也不同。棧常用于保存方法幀和局部變量,而對象總是在堆上分配。棧通常都比堆小,也不會在多個線程之間共享,而堆被整個 JVM 的所有線程共享。

棧:存儲的都是局部變量,凡是定義在方法中的都是<u>局部變量</u>(方法外的是全局變量),for循環(huán)內(nèi)部定義的也是局部變量,在函數(shù)中定義的一些<u>基本類型的變量和對象的引用變量</u>都是在函數(shù)的棧內(nèi)存中分配,當(dāng)在一段代碼塊定義一個變量時,Java 就在棧中為這個變量分配內(nèi)存空間,當(dāng)超過變量的作用域后,Java 會自動釋放掉為該變量分配的內(nèi)存空間,該內(nèi)存空間可以立即被另作它用。棧內(nèi)存的更新速度很快,因為局部變量的生命周期都很短。

堆:堆內(nèi)存用來存放由 new 創(chuàng)建的<u>對象和數(shù)組</u>,在堆中分配的內(nèi)存,由 Java 虛擬機的自動垃圾回收器來管理。在堆中產(chǎn)生了一個數(shù)組或者對象之后,還可以在棧中定義一個特殊的變量,讓棧中的這個變量的取值等于數(shù)組或?qū)ο笤诙褍?nèi)存中的首地址,棧中的這個變量就成了數(shù)組或?qū)ο蟮囊米兞?,以后就可以在程序中使用棧中的引用變量來訪問堆中的數(shù)組或者對象,引用變量就相當(dāng)于是為數(shù)組或者對象起的一個名稱。

4、== 和 equals 的區(qū)別是什么?

== 解讀 ==較的是兩個引用在內(nèi)存中指向的是不是同一對象(即同一內(nèi)存空間),也就是說在內(nèi)存空間中的存儲位置是否一致

對于基本類型和引

用類型 == 的作用效果是不同的,如下所示:

基本類型:比較的是值是否相同;

引用類型:比較的是引用是否相同;

代碼示例:

image.png

equals 解讀

equals 本質(zhì)上就是 ==,只不過 String 和 Integer 等重寫了 equals 方法,把它變成了值比較??聪旅娴拇a就明白了

首先來看默認情況下 equals 比較一個有相同值的對象,代碼如下:[圖片上傳失敗...(image-829f54-1585306775165)]

原來是 String 重寫了 Object 的 equals 方法,把引用比較改成了值比較。

總結(jié):== 對于基本類型來說是值比較,對于引用類型來說是比較的是引用;而 equals 默認情況下是引用比較,只是很多類重新了 equals 方法,比如 String、Integer 等把它變成了值比較,所以一般情況下 equals 比較的是值是否相等。

5、string stringbuffer stringbulider

String底層使用一個不可變的字符數(shù)組private final char value[];所以它內(nèi)容不可變。

StringBuffer和StringBuilder都繼承了AbstractStringBuilder底層使用的是可變字符數(shù)組:char[] value;

6、ConcurentModificationException異常出現(xiàn)的原因

7、java中的switch選擇結(jié)構(gòu)可以使用數(shù)據(jù)類型的數(shù)據(jù)(JDK1.8)?

byte short int char

Byte Short Integer Character

String enum

更好的記憶方法:

基本類型中,沒有boolean和浮點類型+長類型long 相應(yīng)的包裝類型也沒有。外加String和enum。

枚舉類是一種特殊類,它和普通類一樣可以使用構(gòu)造器、定義成員變量和方法,也能實現(xiàn)一個或多個接口,但枚舉類不能繼承其他類。

image.png

8、final關(guān)鍵字

final 修飾的類叫最終類,該類不能被繼承。

final 修飾的方法不能被重寫。

final 修飾的變量叫常量,常量必須初始化,初始化之后值就不能被修改。

使用final關(guān)鍵字修飾一個變量時,<u>是指引用變量不能變</u>,引用變量所指向的對象中的內(nèi)容還是可以改變的。例如,對于如下語句:

Final StringBuffer a=new StringBuffer("immutable"); 執(zhí)行如下語句將報告編譯期錯誤:a=new StringBuffer(""); 但是,執(zhí)行如下語句則可以通過編譯:append(" broken!");

9、下面這條語句一共創(chuàng)建了多少個對象:String s="a"+"b"+"c"+"d";

對于如下代碼:

String s1 = "a";

String s2 = s1 + "b";

String s3 = "a" + "b";

System.out.println(s2 == "ab");

System.out.println(s3 == "ab");

第一條語句打印的結(jié)果為false,第二條語句打印的結(jié)果為true,這說明<u>javac編譯可以對字符串常量直接相加的表達式進行優(yōu)化,不必要等到運行期再去進行加法運算處理</u>,而是在編譯時去掉其中的加號,直接將其編譯成一個這些常量相連的結(jié)果。

題目中的第一行代碼被編譯器在編譯時優(yōu)化后,相當(dāng)于直接定義了一個”abcd”的字符串,所以,上面的代碼應(yīng)該只創(chuàng)建了一個String對象。寫如下兩行代碼,

      String s ="a" + "b" +"c" + "d";

      System.out.println(s== "abcd");

最終打印的結(jié)果應(yīng)該為true。

10、java 中的 Math.round(-1.5) 等于多少?

Math 類中提供了三個與取整有關(guān)的方法:ceil、floor、round,這些方法的作用與它們的英文名稱的含義相對應(yīng),例如,ceil 的英文意義是天花板,該方法就表示向上取整,Math.ceil(11.3)的結(jié)果為 12,Math.ceil(-11.3)的結(jié)果是-11;floor 的英文意義是地板,該方法就表示向下取整,Math.ceil(11.6)的結(jié)果為 11,Math.ceil(-11.6)的結(jié)果是-12;最難掌握的是 round 方法,它表示“四舍五入”,算法為 Math.floor(x+0.5),即將<u>原來的數(shù)字加上</u> <u>0.5</u> <u>后再向下取整</u>,所以,Math.round(11.5)的結(jié)果為 12,Math.round(-11.5)的結(jié)果為-11。

11、Java中實現(xiàn)多態(tài)的機制是什么?

靠的是父類或接口定義的引用變量可以指向子類或具體實現(xiàn)類的實例對象,而程序調(diào)用的方法在運行期才動態(tài)綁定,就是引用變量所指向的具體實例對象的方法,也就是內(nèi)存里正在運行的那個對象的方法,而不是引用變量的類型中定義的方法。

12、普通類和抽象類有哪些區(qū)別?

普通類能被實例化,抽象類不能被實例化。

抽象類中可以有構(gòu)造方法和普通方法

含有抽象方法的類必須聲明為抽象類

抽象方法只需申明,而無需實現(xiàn),抽象類中可以允許普通方法有主體

抽象類的子類必須實現(xiàn)抽象類中的所有抽象方法,除非它的子類也是抽象類

13、抽象類和接口的區(qū)別?

接口可以說成是抽象類的一種特例

抽下該類可以有普通成員變量,接口中的成員變量默認都是public static final類型

抽象類和接口中都可以定義靜態(tài)成員變量,抽象類中的靜態(tài)成員變量的訪問類型可以是任意的,接口中的變量定義的類型只能是public static final

抽象類可以有構(gòu)造方法,接口沒有構(gòu)造方法

抽象類中可以包含非抽象方法,接口中的方法都是抽象方法

抽象類中可以包含靜態(tài)方法,接口中不能包含靜態(tài)方法

接口是對動作的抽象,抽象類是對本質(zhì)的抽象。抽象類表示的是,這個對象是什么。接口表示的是,這個對象能做什么。比如,男人,女人,這兩個類(如果是類的話……),他們的抽象類是人。說明,他們都是人。人可以吃東西,狗也可以吃東西,你可以把“吃東西”定義成一個接口,然后讓這些類去實現(xiàn)它。當(dāng)你關(guān)注一個事物的本質(zhì)的時候,用抽象類;當(dāng)你關(guān)注一個操作的時候,用接口。

14、BIO、NIO、AIO 有什么區(qū)別?

NIO主要有三大核心部分:Channel(通道),Buffer(緩沖區(qū)), Selector。傳統(tǒng)IO基于字節(jié)流和字符流進行操作,而<u>NIO基于Channel和Buffer(緩沖區(qū))進行操作</u>,數(shù)據(jù)總是從通道讀取到緩沖區(qū)中,或者從緩沖區(qū)寫入到通道中。Selector(選擇區(qū))用于監(jiān)聽多個通道的事件(比如:連接打開,數(shù)據(jù)到達)。因此,單個線程可以監(jiān)聽多個數(shù)據(jù)通道。

NIO和傳統(tǒng)IO(一下簡稱IO)之間第一個最大的區(qū)別是,<u>IO是面向流的,NIO是面向緩沖區(qū)的。</u> <u>Java IO面向流意味著每次從流中讀一個或多個字節(jié),直至讀取所有字節(jié),它們沒有被緩存在任何地方。</u>此外,它不能前后移動流中的數(shù)據(jù)。如果需要前后移動從流中讀取的數(shù)據(jù),需要先將它緩存到一個緩沖區(qū)。<u>NIO的緩沖導(dǎo)向方法略有不同。數(shù)據(jù)讀取到一個它稍后處理的緩沖區(qū),</u>需要時可在緩沖區(qū)中前后移動。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩沖區(qū)中包含所有您需要處理的數(shù)據(jù)。而且,需確保當(dāng)更多的數(shù)據(jù)讀入緩沖區(qū)時,不要覆蓋緩沖區(qū)里尚未處理的數(shù)據(jù)。

IO的各種流是阻塞的。這意味著,當(dāng)一個線程調(diào)用read() 或 write()時,該線程被阻塞,直到有一些數(shù)據(jù)被讀取,或數(shù)據(jù)完全寫入。該線程在此期間不能再干任何事情了。 NIO的非阻塞模式,使一個線程從某通道發(fā)送請求讀取數(shù)據(jù),但是它僅能得到目前可用的數(shù)據(jù),如果目前沒有數(shù)據(jù)可用時,就什么都不會獲取。而不是保持線程阻塞,所以直至數(shù)據(jù)變得可以讀取之前,該線程可以繼續(xù)做其他的事情。 非阻塞寫也是如此。一個線程請求寫入一些數(shù)據(jù)到某通道,但不需要等待它完全寫入,這個線程同時可以去做別的事情。 線程通常將非阻塞IO的空閑時間用于在其它通道上執(zhí)行IO操作,所以一個單獨的線程現(xiàn)在可以管理多個輸入和輸出通道(channel)。

BIO:Block IO 同步阻塞式 IO,就是我們平常使用的傳統(tǒng) IO,它的特點是模式簡單使用方便,并發(fā)處理能力低。

NIO:New IO 同步非阻塞 IO,是傳統(tǒng) IO 的升級,客戶端和服務(wù)器端通過 Channel(通道)通訊,實現(xiàn)了多路復(fù)用。

AIO:Asynchronous IO 是 NIO 的升級,也叫 NIO2,實現(xiàn)了異步非堵塞 IO ,異步 IO 的操作基于事件和回調(diào)機制。

15、try-catch-finally 中,如果 catch 中 return 了,finally 還會執(zhí)行嗎?

finally語句總會執(zhí)行

如果try/catch中有return語句,finally中沒有return語句,那么finally中修改數(shù)據(jù)(除集合類、靜態(tài)變量、全局變量)對try/catch中返回的變量沒有任何影響

在finally中使用return,會忽略try/catch中的返回語句,也會忽略try/catch中的異常

finally中如果發(fā)生異常,代碼執(zhí)行將會拋出finally中的異常信息

會執(zhí)行,在 return 前執(zhí)行。<u>finally是在return后面的表達式運算之后執(zhí)行的,此時并沒有返回運算之后的值,而是把值保存起來,不管finally對該值做任何的改變,返回的值都不會改變,依然返回保存起來的值。也就是說方法的返回值是在finally運算之前就確定了的。</u>


image.png

16、error和exception有什么區(qū)別?

Error:Error類對象由 Java虛擬機生成并拋出,大多數(shù)錯誤與代碼編寫者所執(zhí)行的操作無關(guān)。例如,Java虛擬機運行錯誤(Virtual Machine Error),當(dāng)JVM不再有繼續(xù)執(zhí)行操作所需的內(nèi)存資源時,將出現(xiàn)OutOfMemoryError。這些異常發(fā)生時,Java虛擬機(JVM)一般會選擇線程終止;還有發(fā)生在虛擬機試圖執(zhí)行應(yīng)用時,如類定義錯誤(NoClassDefFoundError)、鏈接錯誤(LinkageError)。這些錯誤是不可查的,因為它們在應(yīng)用程序的控制和處理能力之外,而且絕大多數(shù)是程序運行時不允許出現(xiàn)的狀況。對于設(shè)計合理的應(yīng)用程序來說,即使確實發(fā)生了錯誤,本質(zhì)上也不應(yīng)該試圖去處理它所引起的異常狀況。在Java中,錯誤通常是使用Error的子類描述。

Exception:在Exception分支中有一個重要的子類RuntimeException(運行時異常),該類型的異常自動為你所編寫的程序定義ArrayIndexOutOfBoundsException(數(shù)組下標越界)、NullPointerException(空指針異常)、ArithmeticException(算術(shù)異常)、MissingResourceException(丟失資源)、ClassNotFoundException(找不到類)等異常,這些異常是不檢查異常,程序中可以選擇捕獲處理,也可以不處理。這些異常一般是由程序邏輯錯誤引起的,程序應(yīng)該從邏輯角度盡可能避免這類異常的發(fā)生;而RuntimeException之外的異常我們統(tǒng)稱為非運行時異常,類型上屬于Exception類及其子類,從程序語法角度講是必須進行處理的異常,如果不處理,程序就不能編譯通過。如IOException、SQLException等以及用戶自定義的Exception異常,一般情況下不自定義檢查異常。

image.png

17、hashCode() 有什么用?與 a.equals(b) 有什么關(guān)系?

hashCode() 方法對應(yīng)對象整型的 hash 值。它常用于基于 hash 的集合類,如 Hashtable、HashMap、LinkedHashMap等等。它與 equals() 方法關(guān)系特別緊密。根據(jù) Java 規(guī)范,兩個使用 equal() 方法來判斷相等的對象,必須具有相同的 hash code。

set集合是無序不能重復(fù)的,那么怎么能保證不能被放入重復(fù)的元素呢,但靠equals方法一樣比較的話,如果原來集合中以后又10000個元素了,那么放入10001個元素,難道要將前面的所有元素都進行比較,看看是否有重復(fù),這個效率可想而知,因此hashcode就應(yīng)遇而生了,java就采用了hash表,利用哈希算法(也叫散列算法),就是將對象數(shù)據(jù)根據(jù)該對象的特征使用特定的算法將其定義到一個地址上,那么在后面定義進來的數(shù)據(jù)只要看對應(yīng)的hashcode地址上是否有值,那么就用equals比較,如果沒有則直接插入,只要就大大減少了equals的使用次數(shù),執(zhí)行效率就大大提高了。

重寫equals()必須重寫hashCode()

equals()相等的兩個對象,hashcode()一定相等

反過來:hashcode()不等,一定能推出equals()也不等

hashcode()相等,equals()可能相等,也可能不等。

18、兩個對象的 hashCode()相同,則 equals()也一定為 true,對嗎?

不對,兩個對象的 hashCode()相同,equals()不一定 true

image.png

代碼解讀:很顯然“通話”和“重地”的 hashCode() 相同,然而 equals() 則為 false,因為在散列表中,hashCode()相等即兩個鍵值對的哈希值相等,然而哈希值相等,并不一定能得出鍵值對相等。

19、怎么確保一個集合不能被修改?

可以使用 Collections. unmodifiableCollection(Collection c) 方法來創(chuàng)建一個只讀集合,這樣改變集合的任何操作都會拋出 Java. lang. UnsupportedOperationException 異常。

image.png

20、LinkedList 的底層數(shù)據(jù)結(jié)構(gòu)是雙向循環(huán)鏈表

21、如何實現(xiàn)數(shù)組和 List 之間的轉(zhuǎn)換?

List轉(zhuǎn)換成為數(shù)組:調(diào)用ArrayList的toArray方法。

數(shù)組轉(zhuǎn)換成為List:調(diào)用Arrays的asList方法。

22、Array 和 ArrayList 有何區(qū)別?

Array可以容納基本類型和對象,而ArrayList只能容納對象。

Array是指定大小的,而ArrayList大小不是固定的。

Array沒有提供ArrayList那么多功能,比如addAll、removeAll和iterator等。

23、ArrayList 和 Vector 的區(qū)別是什么?

線程安全:Vector 使用了 Synchronized 來實現(xiàn)線程同步,是線程安全的,而 ArrayList 是非線程安全的

性能:ArrayList 在性能方面要優(yōu)于 Vector。

擴容:ArrayList 和Vector 都會根據(jù)實際的需要動態(tài)的調(diào)整容量,只不過在 Vector 擴容每次會增加 1 倍,而ArrayList 只會增加 50%。

24、在 Queue 中 poll()和 remove()有什么區(qū)別?

相同點:都是返回第一個元素,并在隊列中刪除返回的對象。

不同點:如果沒有元素poll()會返回 null,而remove()會直接拋出 NoSuchElementException 異常。

25、HashMap和Hashtable的區(qū)別?

HashMap是非線程安全的,HashTable是線程安全的。

HashMap的鍵和值都允許有null值存在,而HashTable則不行。

因為線程安全的問題,HashMap效率比HashTable的要高。

Hashtable是同步的,而HashMap不是。因此,HashMap更適合于單線程環(huán)境,而Hashtable適合于多線程環(huán)境。但是一般現(xiàn)在不建議用HashTable,

①是HashTable是遺留類,內(nèi)部實現(xiàn)很多沒優(yōu)化和冗余。

②即使在多線程環(huán)境下,現(xiàn)在也有同步的ConcurrentHashMap替代,沒有必要因為是多線程而用HashTable。

26、HaseSet(允許null)實現(xiàn)原理

每次存儲對象的時候, 調(diào)用對象的hashCode()方法, 計算一個哈希值. 在集合中查找是否包含哈希值相同的元素.如果沒有哈希值相同元素, 直接存入.

如果有哈希值相同的元素, 逐個使用equals()方法比較. 比較結(jié)果全為false就存入.

如果比較結(jié)果有true則不存.

27、HaseMap實現(xiàn)原理

當(dāng)添加一個元素(key-value)時,就首先計算元素key的hash值,以此確定插入數(shù)組中的位置,但是可能存在同一hash值的元素已經(jīng)被放在數(shù)組同一位置了,這時就添加到同一hash值的元素的后面,他們在數(shù)組的同一位置,但是形成了鏈表,同一各鏈表上的Hash值是相同的,所以說數(shù)組存放的是鏈表。而當(dāng)鏈表長度太長時,鏈表就轉(zhuǎn)換為<u>紅黑樹(自平衡的二叉查找樹)</u>,這樣大大提高了查找的效率。

28、如何決定使用 HashMap 還是 TreeMap?

對于在 Map 中插入、刪除、定位一個元素這類操作,HashMap 是最好的選擇,因為相對而言 HashMap 的插入會更快,但如果你要對一個 key 集合進行有序的遍歷,那 TreeMap 是更好的選擇。

image.png

29、HashSet與TreeSet的比較

1.TreeSet 是二叉樹實現(xiàn)的,Treeset中的數(shù)據(jù)是自動排好序的,不允許放入null值 。

2.HashSet 是哈希表實現(xiàn)的,HashSet中的數(shù)據(jù)是無序的,可以放入null,但只能放入一個null,兩者中的值都不能重復(fù),就如數(shù)據(jù)庫中唯一約束 。

3.HashSet要求放入的對象必須實現(xiàn)HashCode()方法,放入的對象,是以hashcode碼作為標識的,而具有相同內(nèi)容的String對象,hashcode是一樣,所以放入的內(nèi)容不能重復(fù)。但是同一個類的對象可以放入不同的實例。

HashSet是基于Hash算法實現(xiàn)的,其性能通常都優(yōu)于TreeSet。我們通常都應(yīng)該使用HashSet,在我們需要排序的功能時,我們才使用TreeSet。

30、同步異步?

<u>同步</u>:A線程要請求某個資源,但是此資源正在被B線程使用中,因為同步機制存在,A線程請求不到,怎么辦,A線程只能等待下去 <u>異步</u>:A線程要請求某個資源,但是此資源正在被B線程使用中,因為沒有同步機制存在,A線程仍然請求的到,A線程無需等待

31、關(guān)于線程?

(1) Thread和Runnable的區(qū)別

實現(xiàn)Runnable接口比繼承Thread類所具有的優(yōu)勢:

Runnable的實現(xiàn)方式是實現(xiàn)其接口即可

Thread的實現(xiàn)方式是繼承其類

Thread實現(xiàn)了Runnable接口,提供了更多的可用方法和成員而已。

Thread和Runnable的實質(zhì)是實現(xiàn)關(guān)系,沒有可比性。無論使用Runnable還是Thread,都會new Thread,然后執(zhí)行run方法。用法上,如果有復(fù)雜的線程操作需求,那就選擇繼承Thread,如果只是簡單的執(zhí)行一個任務(wù),那就實現(xiàn)runnable。

  1. Runnable接口支持多繼承,可以避免java中的單繼承的局限性。但基本上用不到

Thread實現(xiàn)了Runnable接口并進行了擴展,而Thread和Runnable的實質(zhì)是實現(xiàn)的關(guān)系,不是同類東西,所以Runnable或Thread本身沒有可比性。

  1. 適合多個相同的程序代碼的線程去共享同一個資源。(錯誤的結(jié)論!https://blog.csdn.net/zhaojianting/article/details/97664370

  2. 線程池只能放入實現(xiàn)Runable或Callable類線程,不能直接放入繼承Thread的類。

(2) 線程安全

如果有多個線程在同時運行,而這些線程可能會同時運行這段代碼。程序每次運行結(jié)果和單線程運行的結(jié)果是一樣的,而且其他的變量的值也和預(yù)期的是一樣的,就是線程安全的。

引起的原因:

線程安全問題都是由<u>全局變量及靜態(tài)變量</u>引起的。若每個線程中對全局變量、靜態(tài)變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執(zhí)行寫操作,一般都需要考慮線程同步,否則的話就可能影響線程安全。

(3) 線程同步

當(dāng)我們使用多 線程訪問同一資源的時候,且多個線程中對資源有寫的操作,就容易出現(xiàn)線程安全問題。 要解決上述多線程并發(fā)訪問一個資源的安全性問題:也就是解決重復(fù)票與不存在票問題,Java中提供了同步機制 (synchronized)來解決。

  1. 同步代碼塊。 2. 同步方法。 3. 鎖機制。

(4) 同步代碼塊

synchronized 關(guān)鍵字可以用于方法中的某個區(qū)塊中,表示只對這個區(qū)塊的資源實行互斥訪問。

image.png

對象的同步鎖只是一個概念,可以想象為在對象上標記了一個鎖.

鎖對象 可以是任意類型。 2. 多個線程對象 要使用同一把鎖。

image.png

(5) 同步方法

使用synchronized修飾的方法,就叫做同步方法,保證A線程執(zhí)行該方法的時候,其他線程只能在方法外 等著。

image.png

同步鎖是誰? 對于非static方法,同步鎖就是this。 對于static方法,我們使用當(dāng)前方法所在類的字節(jié)碼對象(類名.class)。

(6) LOCK鎖

java.util.concurrent.locks.Lock 機制提供了比synchronized代碼塊和synchronized方法更廣泛的鎖定操作, 同步代碼塊/同步方法具有的功能Lock都有,除此之外更強大,更體現(xiàn)面向?qū)ο蟆?/p>

Lock鎖也稱同步鎖,加鎖與釋放鎖方法化了,如下:

public void lock() :加同步鎖。

public void unlock() :釋放同步鎖。

(7) 線程狀態(tài)

1、新建狀態(tài)(New):新創(chuàng)建了一個線程對象。

2、就緒狀態(tài)(Runnable):線程對象創(chuàng)建后,其他線程調(diào)用了該對象的start()方法。該狀態(tài)的線程位于“可運行線程池”中,變得可運行,只等待獲取CPU的使用權(quán)。即在就緒狀態(tài)的進程除CPU之外,其它的運行所需資源都已全部獲得。

3、運行狀態(tài)(Running):就緒狀態(tài)的線程獲取了CPU,執(zhí)行程序代碼。

4、阻塞狀態(tài)(Blocked):阻塞狀態(tài)是線程因為某種原因放棄CPU使用權(quán),暫時停止運行。直到線程進入就緒狀態(tài),才有機會轉(zhuǎn)到運行狀態(tài)。

阻塞的情況分三種:

(1)、等待阻塞:運行的線程執(zhí)行<u>wait</u>()方法,該線程會釋放占用的所有資源,JVM會把該線程放入“等待池”中。進入這個狀態(tài)后,是不能自動喚醒的,必須依靠其他線程調(diào)用notify()或notifyAll()方法才能被喚醒,

(2)、同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM會把該線程放入“鎖池”中。

(3)、其他阻塞:運行的線程執(zhí)行sleep()或join()方法,或者發(fā)出了I/O請求時,JVM會把該線程置為阻塞狀態(tài)。當(dāng)sleep()狀態(tài)超時、join()等待線程終止或者超時、或者<u>I/O</u>處理完畢時,線程重新轉(zhuǎn)入就緒狀態(tài)。

5、死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法,該線程結(jié)束生命周期。

(8) notify()和notifyAll()方法區(qū)別?(wait和notify方法要在同步塊中調(diào)用)

notify()方法不能喚醒某個具體的線程,所以只有一個線程在等待的時候它才有用武之地。而notifyAll()喚醒所有線程并允許他們爭奪鎖確保了至少有一個線程能繼續(xù)運行。

(9) Volatile

是一個特殊的修飾符,只有成員變量才能使用它

· 保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。(實現(xiàn)可見性)

· 禁止進行指令重排序。(實現(xiàn)有序性)(volatile變量可以保證下一個讀取操作會在前一個寫操作之后發(fā)生。)

· volatile 只能保證對單次讀/寫的原子性。i++ 這種操作不能保證原子性。

32、創(chuàng)建線程有三種方式

繼承 Thread 重寫 run 方法;實現(xiàn) Runnable 接口;實現(xiàn) Callable 接口。

33、說一下 runnable 和 callable 有什么區(qū)別?

Runnable從JDK1.0開始就有了,Callable是在JDK1.5增加的。

1)Runnable提供run方法,<u>不會拋出異常</u>,只能在run方法內(nèi)部處理異常。Callable提供call方法,直接拋出Exception異常,也就是你不會因為call方法內(nèi)部出現(xiàn)檢查型異常而不知所措,完全可以拋出即可。

2)Runnable的run方法<u>無返回值</u>,Callable的call方法提供返回值用來表示任務(wù)運行的結(jié)果

3)Runnable可以作為<u>Thread構(gòu)造器的參數(shù)</u>,通過開啟新的線程來執(zhí)行,也可以通過線程池來執(zhí)行。而Callable只能通過線程池執(zhí)行。

34、sleep() 和 wait() 有什么區(qū)別?

每個對象都有一個鎖來控制同步訪問,Synchronized關(guān)鍵字可以和對象的鎖交互,來實現(xiàn)同步方法或同步塊。sleep()方法正在執(zhí)行的線程主動讓出CPU,在sleep指定時間后CPU再回到該線程繼續(xù)往下執(zhí)行(注意:sleep方法只讓出了CPU,而并不會釋放同步資源鎖?。?!);wait()方法則是指當(dāng)前線程讓自己暫時退讓出同步資源鎖,以便其他正在等待該資源的線程得到該資源進而運行,只有調(diào)用了notify()方法,之前調(diào)用wait()的線程才會解除wait狀態(tài),可以去參與競爭同步資源鎖,進而得到執(zhí)行。(注意:notify的作用相當(dāng)于叫醒睡著的人,而并不會給他分配任務(wù),就是說notify只是讓之前調(diào)用wait的線程有權(quán)利重新參與線程的調(diào)度);

sleep()方法可以在任何地方使用;wait()方法則只能在同步方法或同步塊中使用;sleep()是線程線程類(Thread)的方法,調(diào)用會暫停此線程指定的時間,但監(jiān)控依然保持,不會釋放對象鎖,到時間自動恢復(fù);wait()是Object的方法,調(diào)用會放棄對象鎖,進入等待隊列,待調(diào)用notify()/notifyAll()喚醒指定的線程或者所有線程,才會進入鎖池,再次獲得對象鎖才會進入運行狀態(tài);

sleep()方法必須捕獲異常,而wait()、notify()、notifyAll()不需要捕獲異常。

35、線程的 run() 和 start() 有什么區(qū)別?

start() 方法用于啟動線程,run() 方法用于執(zhí)行線程的運行時代碼。run() 可以重復(fù)調(diào)用,而 start() 只能調(diào)用一次。

36、線程池都有哪些狀態(tài)?

1、RUNNING (1) 狀態(tài)說明:線程池處在RUNNING狀態(tài)時,能夠接收新任務(wù),以及對已添加的任務(wù)進行處理。 (2) 狀態(tài)切換:線程池的初始化狀態(tài)是RUNNING。換句話說,線程池被一旦被創(chuàng)建,就處于RUNNING狀態(tài),并且線程池中的任務(wù)數(shù)為0!

2、 SHUTDOWN (1) 狀態(tài)說明:線程池處在SHUTDOWN狀態(tài)時,不接收新任務(wù),但能處理已添加的任務(wù)。 (2) 狀態(tài)切換:調(diào)用線程池的shutdown()接口時,線程池由RUNNING -> SHUTDOWN。

3、STOP (1) 狀態(tài)說明:線程池處在STOP狀態(tài)時,不接收新任務(wù),不處理已添加的任務(wù),并且會中斷正在處理的任務(wù)。 (2) 狀態(tài)切換:調(diào)用線程池的shutdownNow()接口時,線程池由(RUNNING or SHUTDOWN ) -> STOP。

4、TIDYING(tidy :整理,收拾,使整齊) (1) 狀態(tài)說明:當(dāng)所有的任務(wù)已終止,ctl記錄的”任務(wù)數(shù)量”為0,線程池會變?yōu)門IDYING狀態(tài)。當(dāng)線程池變?yōu)門IDYING狀態(tài)時,會執(zhí)行鉤子函數(shù)terminated()。terminated()在ThreadPoolExecutor類中是空的,若用戶想在線程池變?yōu)門IDYING時,進行相應(yīng)的處理;可以通過重載terminated()函數(shù)來實現(xiàn)。 (2) 狀態(tài)切換:當(dāng)線程池在SHUTDOWN狀態(tài)下,阻塞隊列為空并且線程池中執(zhí)行的任務(wù)也為空時,就會由 SHUTDOWN -> TIDYING。 當(dāng)線程池在STOP狀態(tài)下,線程池中執(zhí)行的任務(wù)為空時,就會由STOP -> TIDYING。

5、 TERMINATED (1) 狀態(tài)說明:線程池徹底終止,就變成TERMINATED狀態(tài)。 (2) 狀態(tài)切換:線程池處在TIDYING狀態(tài)時,執(zhí)行完terminated()之后,就會由 TIDYING -> TERMINATED。

image.png

37、ThreadLocal 是什么?有哪些使用場景?

ThreadLocal</u> 是線程本地存儲,在每個線程中都創(chuàng)建了一個 ThreadLocalMap 對象,每個線程可以訪問自己內(nèi)部 ThreadLocalMap 對象內(nèi)的 value。經(jīng)典的使用場景是為每個線程分配一個 JDBC 連接 Connection。這樣就可以保證每個線程的都在各自的 Connection 上進行數(shù)據(jù)庫的操作,不會出現(xiàn) A 線程關(guān)了 B線程正在使用的 Connection; 還有 Session 管理 等問題。

ThreadLocal和Synchronized都是為了解決多線程中相同變量的訪問沖突問題,不同的點是

Synchronized是通過線程等待,犧牲時間來解決訪問沖突

ThreadLocal是通過每個線程單獨一份存儲空間,犧牲空間來解決沖突,并且相比于Synchronized,ThreadLocal具有線程隔離的效果,只有在線程內(nèi)才能獲取到對應(yīng)的值,線程外則不能訪問到想要的值。

38、線程池的組成?

一個線程池包括四個基本部分

1.線程管理池(ThreadPool):用于創(chuàng)建并管理線程池,有創(chuàng)建,銷毀,添加新任務(wù);

2.工作線程(PoolWorker):線程池中的線程在沒有任務(wù)的時候處于等待狀態(tài),可以循

環(huán)的執(zhí)行任務(wù);

3.任務(wù)接口(Task):每個任務(wù)必須實現(xiàn)接口,用來提供工作線程調(diào)度任務(wù)的執(zhí)行,規(guī)定了任務(wù)的入口以及執(zhí)行結(jié)束的收尾工作和任務(wù)的執(zhí)行狀態(tài)等;

4.任務(wù)隊列:用于存放沒有處理的任務(wù),提供一種緩存機制。

39、使用線程池的好處?

·減少了創(chuàng)建和銷毀線程的次數(shù),每個工作線程都可以被重復(fù)利用,可執(zhí)行多個任務(wù)。

·運用線程池能有效的控制線程的最大并發(fā)數(shù),可以根據(jù)系統(tǒng)的承受能力,調(diào)整線程池中工作線程的數(shù)目,防止因為消耗過多的內(nèi)存。

·對線程進行一些簡單的管理,比如:延時執(zhí)行、定時循環(huán)執(zhí)行的策略。

40、常見線程池的種類

image.png

核心線程:

線程池新建線程的時候,如果當(dāng)前線程總數(shù)小于corePoolSize,則新建的是核心線程,如果超過corePoolSize,則新建的是非核心線程

核心線程默認情況下會一直存活在線程池中,即使這個核心線程啥也不干(閑置狀態(tài))。

如果指定ThreadPoolExecutor的allowCoreThreadTimeOut這個屬性為true,那么核心線程如果不干活(閑置狀態(tài))的話,超過一定時間(時長下面參數(shù)決定),就會被銷毀掉

很好理解吧,正常情況下你不干活我也養(yǎng)你,因為我總有用到你的時候,但有時候特殊情況(比如我自己都養(yǎng)不起了),那你不干活我就要把你干掉了

線程總數(shù) = 核心線程數(shù) + 非核心線程數(shù)。核心線程在上面解釋過了,這里說下非核心線程:

不是核心線程的線程

41、java四種常用的線程池實現(xiàn)

1、Executors.newCachedThreadPool():可緩存線程池。

一種線程數(shù)量不定的線程池,并且其最大線程數(shù)為Integer.MAX_VALUE,這個數(shù)是很大的,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。但是線程池中的空閑線程都有超時限制,這個超時時長是60秒,超過60秒閑置線程就會被回收。調(diào)用execute將重用以前構(gòu)造的線程(如果線程可用)。這類線程池比較適合執(zhí)行大量的耗時較少的任務(wù),當(dāng)整個線程池都處于閑置狀態(tài)時,線程池中的線程都會超時被停止。

2、Executors.newFixedThreadPool(int n):創(chuàng)建一個可重用固定個數(shù)的線程池。

當(dāng)線程處于空閑狀態(tài)時,它們并不會被回收,除非線程池被關(guān)閉了,如果工作線程數(shù)量達到線程池初始的最大數(shù),則將提交的任務(wù)存入到池隊列(沒有大小限制)中。由于newFixedThreadPool只有核心線程并且這些核心線程不會被回收,這樣它更加快速底相應(yīng)外界的請求。

·3、Executors.newScheduledThreadPool(int n):創(chuàng)建一個定長線程池,支持定時及周期性任務(wù)執(zhí)行。

它的線程數(shù)量是固定的,它可安排給定延遲后運行命令或者定期地執(zhí)行,這類線程池主要用于執(zhí)行定時任務(wù)和具有固定周期的重復(fù)任務(wù)。

4、Executors.newSingleThreadExecutor():創(chuàng)建一個單線程化的線程池,它只會用唯一的工作線程來執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級)執(zhí)行。

42、** 什么是ORM?**

對象關(guān)系映射(Object-Relational Mapping,簡稱ORM)是一種為了解決程序的面向?qū)ο竽P团c數(shù)據(jù)庫的關(guān)系模型互不匹配問題的技術(shù);

簡單的說,ORM是通過使用描述對象和數(shù)據(jù)庫之間映射的元數(shù)據(jù)(在Java中可以用XML或者是注解),將程序中的對象自動持久化到關(guān)系數(shù)據(jù)庫中或者將關(guān)系數(shù)據(jù)庫表中的行轉(zhuǎn)換成Java對象,其本質(zhì)上就是將數(shù)據(jù)從一種形式轉(zhuǎn)換到另外一種形式。

43、mybatis 中 #{}和 ${}的區(qū)別是什么?

{}是預(yù)編譯處理,${}是字符串替換;

Mybatis在處理#{}時,會將sql中的#{}替換為?號,調(diào)用PreparedStatement的set方法來賦值;
Mybatis在處理{}時,就是把{}替換成變量的值;
使用#{}可以有效的防止SQL注入,提高系統(tǒng)安全性。
{}在什么情況下使用呢?有時候可能需要直接插入一個不做任何修改的字符串到SQL語句中。這時候應(yīng)該使用{}語法。比如,動態(tài)SQL中的字段名,如:ORDER BY ${columnName}

將傳入的數(shù)據(jù)都當(dāng)成一個字符串,會對自動傳入的數(shù)據(jù)加一個雙引號。

如:where username=#{username},如果傳入的值是111,那么解析成sql時的值為where username="111", 如果傳入的值是id,則解析成的sql為where username="id". 
將傳入的數(shù)據(jù)直接顯示生成在sql中。 如:where username={username},如果傳入的值是111,那么解析成sql時的值為where username=111;
針對上面的sql,如果傳入的值是;drop table user;,
那么第一條用#{}的sql解析為:select id, username, password, role from user where username=";drop table user;"
那么第二條用{}的sql解析為:select id, username, password, role from user where username=;drop table user;這時候已經(jīng)sql注入了。方式一般用于傳入數(shù)據(jù)庫對象,例如傳入表名和列名,還有排序時使用order by動態(tài)參數(shù)時需要使用,ORDER BY{columnName}

image.png

44、RowBounds 是一次性查詢?nèi)拷Y(jié)果嗎?為什么?

RowBounds 表面是在“所有”數(shù)據(jù)中檢索數(shù)據(jù),其實并非是一次性查詢出所有數(shù)據(jù),因為 MyBatis 是對 jdbc 的封裝,在 jdbc 驅(qū)動中有一個 Fetch Size 的配置,它規(guī)定了每次最多從數(shù)據(jù)庫查詢多少條數(shù)據(jù),假如你要查詢更多數(shù)據(jù),它會在你執(zhí)行 next()的時候,去查詢更多的數(shù)據(jù)。就好比你去自動取款機取 10000 元,但取款機每次最多能取 2500 元,所以你要取 4 次才能把錢取完。只是對于 jdbc 來說,當(dāng)你調(diào)用 next()的時候會自動幫你完成查詢工作。這樣做的好處可以有效的防止內(nèi)存溢出。

45、redis什么是緩存穿透?怎么解決?

緩存穿透:指查詢一個一定不存在的數(shù)據(jù),由于緩存是不命中時需要從數(shù)據(jù)庫查詢,查不到數(shù)據(jù)則不寫入緩存,這將導(dǎo)致這個不存在的數(shù)據(jù)每次請求都要到數(shù)據(jù)庫去查詢,造成緩存穿透。

解決方案:最簡單粗暴的方法如果一個查詢返回的數(shù)據(jù)為空(不管是數(shù)據(jù)不存在,還是系統(tǒng)故障),我們就把這個空結(jié)果進行緩存,但它的過期時間會很短,最長不超過五分鐘。

緩存雪崩,是指在某一個時間段,緩存集中過期失效。

產(chǎn)生雪崩的原因之一,比如在寫本文的時候,馬上就要到雙十二零點,很快就會迎來一波搶購,這波商品時間比較集中的放入了緩存,假設(shè)緩存一個小時。那么到了凌晨一點鐘的時候,這批商品的緩存就都過期了。而對這批商品的訪問查詢,都落到了數(shù)據(jù)庫上,對于數(shù)據(jù)庫而言,就會產(chǎn)生周期性的壓力波峰。

小編在做電商項目的時候,一般是采取不同分類商品,緩存不同周期。在同一分類中的商品,加上一個隨機因子。這樣能盡可能分散緩存過期時間,而且,熱門類目的商品緩存時間長一些,冷門類目的商品緩存時間短一些,也能節(jié)省緩存服務(wù)的資源。

緩存擊穿,是指一個key非常熱點,在不停的扛著大并發(fā),大并發(fā)集中對這一個點進行訪問,當(dāng)這個key在失效的瞬間,持續(xù)的大并發(fā)就穿破緩存,直接請求數(shù)據(jù)庫,就像在一個屏障上鑿開了一個洞。

小編在做電商項目的時候,把這貨就成為“爆款”。

其實,大多數(shù)情況下這種爆款很難對數(shù)據(jù)庫服務(wù)器造成壓垮性的壓力。達到這個級別的公司沒有幾家的。所以,務(wù)實主義的小編,對主打商品都是早早的做好了準備,讓緩存永不過期。即便某些商品自己發(fā)酵成了爆款,也是直接設(shè)為永不過期就好了。

46、redis 支持的 java 客戶端都有哪些?

Redisson、Jedis、lettuce等等,官方推薦使用Redisson。

47、怎么保證緩存和數(shù)據(jù)庫數(shù)據(jù)的一致性?

合理設(shè)置緩存的過期時間。

新增、更改、刪除數(shù)據(jù)庫操作時同步更新 Redis,可以使用事物機制來保證數(shù)據(jù)的一致性

48、redis持久化有幾種方式?

Redis 的持久化有兩種方式,或者說有兩種策略:

RDB(Redis Database):指定的時間間隔能對你的數(shù)據(jù)進行快照存儲。

AOF(Append Only File):每一個收到的寫命令都通過write函數(shù)追加到文件中。

49、Jedis操作各種redis中的數(shù)據(jù)結(jié)構(gòu)

  1. 字符串類型 string (Set Get)

//存儲 jedis.set("username","zhangsan");

//獲取 String username = jedis.get("username");

//可以使用setex()方法存儲可以指定過期時間的 key value

jedis.setex("activecode",20,"hehe");//將activecode:hehe鍵值對存入redis,并且20秒后自動刪除該鍵值對

  1. 哈希類型 hash : map格式 (hset hget hgetAll)

// 存儲hash jedis.hset("user","name","lisi");

// 獲取hash String name = jedis.hget("user", "name");

// 獲取hash的所有map中的數(shù)據(jù) Map<String, String> user = jedis.hgetAll("user");

// keyset Set<String> keySet = user.keySet();

    for (String key : keySet) {

        //獲取value

        String value = user.get(key);

        System.out.println(key + ":" + value);

    }

3)列表類型 list : linkedlist格式。支持重復(fù)元素

lpush / rpush

lpop / rpop 彈出

lrange start end : 范圍獲取

// list 存儲

    jedis.lpush("mylist","a","b","c");//從左邊存

    jedis.rpush("mylist","a","b","c");//從右邊存

    // list 范圍獲取

    List<String> mylist = jedis.lrange("mylist", 0, -1);

    System.out.println(mylist);

    // list 彈出

    String element1 = jedis.lpop("mylist");//c

    System.out.println(element1);

    String element2 = jedis.rpop("mylist");//c

    System.out.println(element2);

    // list 范圍獲取

    List<String> mylist2 = jedis.lrange("mylist", 0, -1);
  1. 集合類型 set : 不允許重復(fù)元素

sadd

smembers:獲取所有元素

// set 存儲

    jedis.sadd("myset","java","php","c++");

    // set 獲取

    Set<String> myset = jedis.smembers("myset");

  jedis.close();
  1. 有序集合類型 sortedset:不允許重復(fù)元素,且元素有順序

zadd

Zrange

50、RabbitMQ(微秒級延遲)

(1)消息隊列概述:

消息隊列是應(yīng)用程序之間的通信方法;無需即時返回的且耗時的操作進行異步處理從而提高系統(tǒng)的吞吐量;可以實現(xiàn)程序之間的解耦合

  • 實現(xiàn)方式:AMQP(協(xié)議),JMS(Java消息服務(wù)(JavaMessage Service)應(yīng)用程序接口java平臺中關(guān)于面向消息中間件(MOM)的API)

  • 常見產(chǎn)品:activeMQ,zeroMQ,RabbitMQ(AMQP,基于erlang語言開發(fā)的),RocketMQ,kafka

(2)消息隊列的優(yōu)缺點:

優(yōu)點:常見使用場景有很多但是核心的有三個:解耦、異步、削峰

缺點:

系統(tǒng)可用性降低:系統(tǒng)引入的外部依賴越多,越容易掛掉,本來你就是A系統(tǒng)調(diào)用BCD三個系統(tǒng)的接口就好了,人ABCD四個系統(tǒng)好好的沒什么問題,你偏加個MQ進來,萬一MQ掛了怎么辦,整套系統(tǒng)崩潰了,就完蛋了

系統(tǒng)復(fù)雜性提高:硬生生加個MQ進來,你怎么保證消息沒有重復(fù)消費?怎么處理消息丟失的情況?

一致性問題:系統(tǒng)A處理完了直接返回成功了,人家都認為你這個請求成功了;但問題是,要是BCD三個系統(tǒng)哪里BD系統(tǒng)成功了,結(jié)果C系統(tǒng)寫庫失敗了,咋整?數(shù)據(jù)就不一致了?

(3)解決RabbitMQ數(shù)據(jù)丟失問題

① 生產(chǎn)者弄丟數(shù)據(jù)

一般來說,如果你要確保說寫rabbitmq的消息別丟,可以開啟confirm模式,在生產(chǎn)者那里<u>設(shè)置開啟confirm模式</u>(<u>異步機制</u>)之后,你每次寫的消息都會分配一個唯一的id,然后如果寫入了rabbitmq中,rabbitmq會給你回傳一個ack消息,告訴你說這個消息ok了。如果超過一定時間還沒接收到這個消息的回調(diào),那么你可以重發(fā)。

還可以選擇用rabbitmq提供的事務(wù)功能(<u>同步機制</u>),就是生產(chǎn)者發(fā)送數(shù)據(jù)之前開啟rabbitmq事務(wù)(channel.txSelect),然后發(fā)送消息,如果消息沒有成功被rabbitmq接收到,那么生產(chǎn)者會收到異常報錯,此時就可以回滾事務(wù)(channel.txRollback),然后重試發(fā)送消息;如果收到了消息,那么可以提交事務(wù)(channel.txCommit)。但是問題是,rabbitmq事務(wù)機制一搞,基本上吞吐量會下來,因為太耗性能。

② RabbitMQ弄丟數(shù)據(jù)

這個你必須<u>開啟rabbitmq的持久化</u>,就是消息寫入之后會持久化到磁盤,哪怕是rabbitmq自己掛了,恢復(fù)之后會自動讀取之前存儲的數(shù)據(jù),一般數(shù)據(jù)不會丟。除非極其罕見的是,rabbitmq還沒持久化,自己就掛了,可能導(dǎo)致少量數(shù)據(jù)會丟失的,但是這個概率較小。而且<u>持久化可以跟生產(chǎn)者那邊的confirm機制配合起來,只有消息被持久化到磁盤之后,才會通知生產(chǎn)者ack了,所以哪怕是在持久化到磁盤之前,rabbitmq掛了,數(shù)據(jù)丟了,生產(chǎn)者收不到ack,你也是可以自己重發(fā)的。</u>

哪怕是你給rabbitmq開啟了持久化機制,也有一種可能,就是這個消息寫到了rabbitmq中,但是還沒來得及持久化到磁盤上,結(jié)果不巧,此時rabbitmq掛了,就會導(dǎo)致內(nèi)存里的一點點數(shù)據(jù)會丟失。

③ 消費者弄丟數(shù)據(jù)

rabbitmq如果丟失了數(shù)據(jù),主要是因為你消費的時候,剛消費到,還沒處理,結(jié)果進程掛了,比如重啟了,那么就尷尬了,rabbitmq認為你都消費了,這數(shù)據(jù)就丟了。

這個時候得用rabbitmq提供的ack機制,簡單來說,就是你關(guān)閉rabbitmq自動ack,可以通過一個api來調(diào)用就行,然后每次你自己代碼里確保處理完的時候,再程序里ack一把。這樣的話,如果你還沒處理完,不就沒有ack?那rabbitmq就認為你還沒處理完,這個時候rabbitmq會把這個消費分配給別的consumer去處理,消息是不會丟的。

(4)如何保證消息的順序性?

一個queue,多個consumer時:

拆分多個queue,每個queue一個consumer,就是多一些queue而已,確實是麻煩點。

(5)消息基于什么傳輸?

由于 TCP 連接的創(chuàng)建和銷毀開銷較大,且并發(fā)數(shù)受系統(tǒng)資源限制,會造成性能瓶頸。RabbitMQ 使用信道的方式來傳輸數(shù)據(jù)。信道是建立在真實的 TCP 連接內(nèi)的虛擬連接,且每條 TCP 連接上的信道數(shù)量沒有限制。

(6)生產(chǎn)者實現(xiàn)發(fā)送消息的步驟:

1. 創(chuàng)建連接工廠(設(shè)置RabbitMQ的連接參數(shù));

2. 創(chuàng)建連接;

3. 創(chuàng)建頻道;

4. 聲明隊列;

5. 發(fā)送消息;

6. 關(guān)閉資源

(7)實現(xiàn)消費者步驟:

1. 創(chuàng)建連接工廠;

2. 創(chuàng)建連接;(抽取一個獲取連接的工具類)

3. 創(chuàng)建頻道;

4. 聲明隊列;

5. 創(chuàng)建消費者(接收消息并處理消息);

6. 監(jiān)聽隊列

(8)5種模式特征

(1)不直接Exchange交換機(默認交換機)

simple簡單模式:一個生產(chǎn)者生產(chǎn)一個消息到一個隊列被一個消費者接收

work工作隊列模式:生產(chǎn)者發(fā)送消息到一個隊列中,然后可以被多個消費者監(jiān)聽該隊列;一個消息只能被一個消費者接收,消費者之間是競爭關(guān)系

(2)使用Exchange交換機;訂閱模式(交換機:廣播fanout、定向direct、通配符topic)發(fā)布與訂閱模式:使用了fanout廣播類型的交換機,可以將一個消息發(fā)送到所有綁定了該交換機的隊列

路由模式:使用了direct定向類型的交換機,消費會攜帶路由key,交換機根據(jù)消息的路由key與隊列的路由key進行對比,一致的話那么該隊列可以接收到消息

通配符模式:使用了topic通配符類型的交換機,消費會攜帶路由key(*, #),交換機根據(jù)消息的路由key與隊列的路由key進行對比,匹配的話那么該隊列可以接收到消息。

(9)RabbitMQ 怎么避免消息丟失?

把消息持久化磁盤,保證服務(wù)器重啟消息不丟失。

每個集群中至少有一個物理磁盤,保證消息落入磁盤。

(10)RabbitMQ 有哪些重要的組件?

ConnectionFactory(連接管理器):應(yīng)用程序與Rabbit之間建立連接的管理器,程序代碼中使用。

Channel(信道):消息推送使用的通道。

Exchange(交換器):用于接受、分配消息。

Queue(隊列):用于存儲生產(chǎn)者的消息。

RoutingKey(路由鍵):用于把生成者的數(shù)據(jù)分配到交換器上。

BindingKey(綁定鍵):用于把交換器的消息綁定到隊列上。

51、Dubbo和Zookeeper

(1) SOA(Service Oriented Architecture)“面向服務(wù)的架構(gòu)”:他是一種設(shè)計方法其中包含多個服務(wù), 服務(wù)之間通過相互依賴最終提供一系列的功能。一個服務(wù)通常以獨立的形式存在與操作系統(tǒng)進程中。各個服務(wù)之間通過網(wǎng)絡(luò)調(diào)用。

(2) Dubbo簡介

Apache Dubbo是一款高性能的Java RPC框架,RPC全稱為remote procedure call,即遠程過程調(diào)用,Dubbo提供了三大核心能力:<u>面向接口的遠程方法調(diào)用,智能容錯和負載均衡,以及服務(wù)自動注冊和發(fā)現(xiàn)。</u>

(3) Dubbo架構(gòu)

image.png

image.png

虛線都是異步訪問,實線都是同步訪問

藍色虛線:在啟動時完成的功能

紅色虛線(實線)都是程序運行過程中執(zhí)行的功能

調(diào)用關(guān)系說明:

0. 服務(wù)容器負責(zé)啟動,加載,運行服務(wù)提供者。

1. 服務(wù)提供者在啟動時,向注冊中心注冊自己提供的服務(wù)。

2. 服務(wù)消費者在啟動時,向注冊中心訂閱自己所需的服務(wù)。

3. 注冊中心返回服務(wù)提供者地址列表給消費者,如果有變更,注冊中心將基于長連接推送變更數(shù)據(jù)給消費者。

4. 服務(wù)消費者,從提供者地址列表中,基于軟負載均衡算法,選一臺提供者進行調(diào)用,如果調(diào)用失敗,再選另一臺調(diào)用。

5. 服務(wù)消費者和提供者,在內(nèi)存中累計調(diào)用次數(shù)和調(diào)用時間,定時每分鐘發(fā)送一次統(tǒng)計數(shù)據(jù)到監(jiān)控中心。

(4) 服務(wù)注冊中心Zookeeper(樹型的目錄服務(wù))

通過前面的Dubbo架構(gòu)圖可以看到,Registry(服務(wù)注冊中心)在其中起著至關(guān)重要的作用。Dubbo官方推薦使用Zookeeper作為服務(wù)注冊中心。

(5) 如何防止Zookeeper單點故障?

Zookeeper其實是支持集群模式的,可以配置Zookeeper集群來達到Zookeeper服務(wù)的高可用,防止出現(xiàn)單點故障。

(6) Dubbo管理控制臺

我們在開發(fā)時,需要知道Zookeeper注冊中心都注冊了哪些服務(wù),有哪些消費者來消費這些服務(wù)。我們可以通過部署一個管理中心來實現(xiàn)。其實管理中心就是一個web應(yīng)用,部署到tomcat即可。

(7) 解決Dubbo無法發(fā)布被事務(wù)代理的Service問題

① 修改applicationContext-service.xml配置文件,開啟事務(wù)控制注解支持時指定proxy-target-class屬性,值為true。其作用是使用cglib代理方式為Service類創(chuàng)建代理對象

<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>

② 修改HelloServiceImpl類,在Service注解中加入interfaceClass屬性,值為HelloService.class,作用是指定服務(wù)的接口類型

52、Mysql及其優(yōu)化

(1)where子句的作用是在對查詢結(jié)果進行分組前,將不符合where條件的行去掉,即在<u>分組之前過濾數(shù)據(jù)</u>,條件中不能包含聚組函數(shù),使用where條件顯示特定的行。

(2)having 子句的作用是篩選滿足條件的組,即在<u>分組之后過濾數(shù)據(jù)</u>,條件中經(jīng)常包含聚組函數(shù),使用having 條件顯示特定的組,也可以使用多個分組標準進行分組。

(3)當(dāng)distinct應(yīng)用到多個字段的時候,其應(yīng)用的范圍是其后面的所有字段,而不只是緊挨著它的一個字段,而且distinct只能放到所有字段的前面,如下語句是錯誤的:SELECT country, distinct province from person; // 該語句是錯誤的,distinct對NULL是不進行過濾的,即返回的結(jié)果中是包含NULL值的代表整列,使用distinct對操作select DISTINCT * from person相當(dāng)于select DISTINCT id, name, country, province, city from person;

(4)isnull(exper) 判斷exper是否為空,是則返回1,否則返回0

(5)ifnull(exper1,exper2)判斷exper1是否為空,是則用exper2代替;

(6)nullif(exper1,exper2)如果expr1=expr2 成立,那么返回值為NULL,否則返回值為expr1

(7)if(expr1,expr2,expr3),如果expr1的值為true,則返回expr2的值,如果expr1的值為false,則返回expr3的值。

(8)<u>SQL語句中IN包含的值不應(yīng)過多</u>:MySQL對于IN做了相應(yīng)的優(yōu)化,即將IN中的常量全部存儲在一個數(shù)組里面,而且這個數(shù)組是排好序的。但是如果數(shù)值較多,產(chǎn)生的消耗也是比較大的。再例如:select id from t where num in(1,2,3) 對于連續(xù)的數(shù)值,能用between就不要用in了;再或者使用連接來替換。

(9)應(yīng)盡量避免在 where 子句中對字段進行 null 值判斷,否則將導(dǎo)致引擎放棄使用索引而進行全表掃描,如:select id from t where num is null可以在num上設(shè)置默認值0,確保表中num列沒有null值,然后這樣查詢:select id from t where num=0

(10)SELECT語句務(wù)必指明字段名稱:SELECT*增加很多不必要的消耗。

(11)使用連接(JOIN)來代替子查詢(Sub-Queries)

(12)當(dāng)只需要一條數(shù)據(jù)的時候,使用limit 1

(13)如果排序字段沒有用到索引,就盡量少排序

(14)區(qū)分in和exist、not in 和not exists

(15)select * from 表A where id in (select id from 表B)上面SQL語句相當(dāng)于select * from 表A where exists(select * from 表B where 表B.id=表A.id)區(qū)分in和exists主要是造成了驅(qū)動順序的改變(這是性能變化的關(guān)鍵),如果是exists,那么以外層表為驅(qū)動表,先被訪問,如果是IN,那么先執(zhí)行子查詢。所以IN適合于外表大而內(nèi)表小的情況;EXISTS適合于外表小而內(nèi)表大的情況。

(16)關(guān)于not in和not exists,推薦使用not exists,不僅僅是效率問題,not in可能存在邏輯問題。如何高效的寫出一個替代not exists的SQL語句?原SQL語句:select colname … from A表 where a.id not in (select b.id from B表)高效的SQL語句:select colname … from A表 Left join B表 on where a.id = b.id where b.id is null取出的結(jié)果集如下圖表示,A表不在B表中的數(shù)據(jù)


image.png

(17)避免在where子句中對字段進行表達式操作

(18)比如:select user_id,user_project from user_base where age*2=36;中對字段就行了算術(shù)運算,這會造成引擎放棄使用索引,建議改成:select user_id,user_project from user_base where age=36/2;

(19)事務(wù)

① 四大特性

原子性(A):事務(wù)是最小單位,不可再分

一致性(C):事務(wù)要求所有的DML語句操作的時候,必須保證同時成功或者同時失敗

隔離性(I):事務(wù)A和事務(wù)B之間具有隔離性

持久性(D):是事務(wù)的保證,事務(wù)終結(jié)的標志(內(nèi)存的數(shù)據(jù)持久到硬盤文件中)

② 隔離性有隔離級別(4個)

讀未提交:read uncommitted

讀已提交:read committed

可重復(fù)讀:repeatable read

串行化:serializable

(20)范式

1NF的關(guān)系中的每個屬性都不可再分。

2NF在1NF的基礎(chǔ)之上,消除了非主屬性對于碼的部分函數(shù)依賴。

3NF在2NF的基礎(chǔ)之上,消除了非主屬性對于碼的傳遞函數(shù)依賴。

BCNF在3NF的基礎(chǔ)之上,消除主屬性對于碼的部分與傳遞函數(shù)依賴。

(21)索引

索引是對數(shù)據(jù)庫表中一列或多列的值進行排序的一種結(jié)構(gòu),使用索引可快速訪問數(shù)據(jù)庫表中的特定信息。

舉例說明索引:如果把數(shù)據(jù)庫中的某一張看成一本書,那么索引就像是書的目錄,可以通過目錄快速查找書中指定內(nèi)容的位置,對于數(shù)據(jù)庫表來說,可以通過索引快速查找表中的數(shù)據(jù)。

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

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

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