中高階Java面試題

清和_001發表於2020-11-09

同學整理到有道雲上面的,公司把有道雲禁了,所以弄到這上面方便看,順便有需要的也可以看看

常見演算法相關:

  1. 二分法

使用二分查詢(Binary Search)的前提有:

(1)線性表必須是關鍵碼有序(通常是從小到大有序)

(2)其次,線性表必須是順序儲存。所以連結串列不能採用二分查詢。

二分查詢(Binary Search)基本思想:在有序表中,取中間記錄作為比較物件,若給定值與中間記錄的關鍵字相等,則查詢成功;若給定值小於中間記錄的關鍵字,則在中間記錄的左半區繼續查詢;若給定值大於中間記錄的關鍵字,則在中間記錄的右半區繼續查詢。不斷重複上述過程,知道查詢成功,或者所有查詢區域無記錄,查詢失敗為止。

java程式碼

  1. 氣泡排序

重複地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重複地進行直到沒有再需要交換,也就是說該數列已經排序完成。名字由來是因為越大的元素會經由交換慢慢“浮”到數列的頂端,故名。

     比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。在這一點,最後的元素應該會是最大的數。針對所有的元素重複以上的步驟,除了最後一個。持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。

程式碼示例如下:

給出一堆大批量資料(比如10億),如何從中快速查詢出前N個最大值?

  解決方案:採用最小堆的形式。先取出N個資料生成最小堆,然後再取出後面的值依次與堆頂比較,比堆頂小,則繼續;比堆頂大,則交換值,並更新最小堆,直到所有值全部比較完,最後生成的最小堆就是最大的N個資料。

  最小堆概念:最小堆是一種經過排序的完全二叉樹,其中任一非終端節點的資料值均不大於其左子節點和右子節點的值。如下圖所示,即有節點2、節點3大於等於節點1;節點4、節點5大於等於節點2;節點6、節點7大於等於節點3。

最小堆的例項如下所示:

常見設計模式:

  1. 工廠模式
  2. 構造器模式

上圖是Strategy 模式的結構圖,讓我們可以進行更方便的描述:

  1. Builder:為建立一個Product物件的各個部件指定抽象介面。
  2. ConcreteBuilder:實現Builder的介面以構造和裝配該產品的各個部件,定義並明確它所建立的表示,提供一個檢索產品的介面
  3. Director:構造一個使用Builder介面的物件。
  4. Product:表示被構造的複雜物件。ConcreateBuilder建立該產品的內部表示並定義它的裝配過程。                  
  5. 單例模式

單例模式確保某個類只有一個例項,而且自行例項化並向整個系統提供這個例項。

單例模式就是為了避免不一致狀態

懶漢式單例

懶漢式單例的實現沒有考慮執行緒安全問題,它是執行緒不安全的,併發環境下很可能出現多個Singleton例項

餓漢式單例

餓漢式在類建立的同時就已經建立好一個靜態的物件供系統使用,以後不再改變,所以是執行緒安全的。

  1. 多例模式

多例模式的特點跟單例模式不同的是,在類中定義了有限個可例項化的物件個數;目的是提供對此類有多個訪問入口,在多執行緒模式下可提高非同步效率。

  1. 組裝器

基礎概念相關:

  1. Java基本型別

byte:8位,最大儲存資料量是255,存放的資料範圍是-128~127之間。

short:16位,最大資料儲存量是65536,資料範圍是-32768~32767之間。

int:32位,最大資料儲存容量是2的32次方減1,資料範圍是負的2的31次方到正的2的31次方減1。

long:64位,最大資料儲存容量是2的64次方減1,資料範圍為負的2的63次方到正的2的63次方減1。

float:32位,資料範圍在3.4e-45~1.4e38,直接賦值時必須在數字後加上f或F。

double:64位,資料範圍在4.9e-324~1.8e308,賦值時可以加d或D也可以不加。

boolean:只有true和false兩個取值。

char:16位,儲存Unicode碼,用單引號賦值。

在java中String類為什麼要設計成final?

* 從設計安全上講 

1、確保它們不會在子類中改變語義。String類是final類,這意味著不允許任何人定義String的子類。

換言之,

如果有一個String的引用,它引用的一定是一個String物件,而不可能是其他類的物件。 

2、String 一旦被建立是不能被修改的,因為 java 設計者將 String 為可以共享的

* 從效率上講: 

1、設計成final,JVM才不用對相關方法在虛擬函式表中查詢,而直接定位到String類的相關方法上,提高了執行效率。 

2、Java設計者認為共享帶來的效率更高。

總而言之,就是要保證 java.lang.String 引用的物件一定是 java.lang.String的物件,而不是引用它的子孫類,這樣才能保證它的效率和安全。

 

為什麼Java中1000==1000為false而100==100為true?

我們知道,如果兩個引用指向同一個物件,用==表示它們是相等的。如果兩個引用指向不同的物件,用==表示它們是不相等的,即使它們的內容相同。

因此,後面一條語句也應該是false 

這就是它有趣的地方了。如果你看去看 Integer.java 類,你會發現有一個內部私有類,IntegerCache.java,它快取了從-128到127之間的所有的整數物件如果值的範圍在-128到127之間,它就從快取記憶體返回例項。

String,StringBuffer與StringBuilder的區別?

String 字串常量

StringBuffer 字串變數(執行緒安全)

StringBuilder 字串變數(非執行緒安全)

String 型別和 StringBuffer 型別的主要效能區別其實在於 String 是不可變的物件, 因此在每次對 String 型別進行改變的時候其實都等同於生成了一個新的 String 物件,然後將指標指向新的 String 物件,所以經常改變內容的字串最好不要用 String ,因為每次生成物件都會對系統效能產生影響,特別當記憶體中無引用物件多了以後, JVM 的 GC 就會開始工作,那速度是一定會相當慢的。

在大部分情況下 StringBuilder >StringBuffer > String 

StringBuffer

Java.lang.StringBuffer執行緒安全的可變字元序列。一個類似於 String 的字串緩衝區,但不能修改。

java.lang.StringBuilde

java.lang.StringBuilder一個可變的字元序列是5.0新增的。此類提供一個與 StringBuffer 相容的 API,但不保證同步。

String宣告為final類的目的:

用final關鍵字修飾,這說明String不可繼承;保證了安全 如果有一個String的引用,它引用的一定是一個String物件,而不可能是其他類的物件。

保證String是不可變(immutable)。不可變就是第二次給一個String 變數賦值的時候,不是在原記憶體地址上修改資料,而是重新指向一個新物件,新地址。

Java 動態代理

 動態代理技術就是用來產生一個物件的代理物件的

 所以在這裡明確代理物件的兩個概念:

    1、代理物件存在的價值主要用於攔截對真實業務物件的訪問

    2、代理物件應該具有和目標物件(真實業務物件)相同的方法

 在java中規定,要想產生一個物件的代理物件,那麼這個物件必須要有一個介面

java在JDK1.5之後提供了一個"java.lang.reflect.Proxy"類,通過"Proxy"類提供的一個newProxyInstance方法用來建立一個物件的代理物件

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

 newProxyInstance方法用來返回一個代理物件,這個方法總共有3個引數,ClassLoader loader用來指明生成代理物件使用哪個類裝載器,Class<?>[] interfaces用來指明生成哪個物件的代理物件,通過介面指定,InvocationHandler h用來指明產生的這個代理物件要做什麼事情。所以我們只需要呼叫newProxyInstance方法就可以得到某一個物件的代理物件了。

https://blog.csdn.net/m0_38039437/article/details/77970633

Java 反射:

Java 裡的反射機制在預設情況下,方法的反射呼叫為委派實現委派給本地實現來進行方法呼叫。在呼叫超過 15 次之後,委派實現便會將委派物件切換至動態實現。這個動態實現的位元組碼是自動生成的,它將直接使用 invoke 指令來呼叫目標方法。

方法的反射呼叫會帶來不少效能開銷,原因主要有三個:變長引數方法導致的 Object 陣列,基本型別的自動裝箱、拆箱,還有最重要的方法內聯

 

Java 抽象類和介面的區別及具體使用場景

1.一個類可以實現多個介面 ,但卻只能繼承最多一個抽象類。

2.抽象類可以包含具體的方法 , 介面的所有方法都是抽象的。

3.抽象類可以宣告和使用欄位 ,介面則不能,但介面可以建立靜態的final常量。

4.介面的方法都是public的,抽象類的方法可以是public,protected,private或者預設的package;

5.抽象類可以定義建構函式,介面卻不能。 

 interface的應用場合

類與類之前需要特定的介面進行協調,而不在乎其如何實現。

作為能夠實現特定功能的標識存在,也可以是什麼介面方法都沒有的純粹標識。

需要將一組類視為單一的類,而呼叫者只通過介面來與這組類發生聯絡。

需要實現特定的多項功能,而這些功能之間可能完全沒有任何聯絡。

 abstract class的應用場合

一句話,在既需要統一的介面,又需要例項變數或預設的方法的情況下,就可以使用它。最常見的有:定義了一組介面,但又不想強迫每個實現類都必須實現所有的介面。可以用abstract class定義一組方法體,甚至可以是空方法體,然後由子類選擇自己所感興趣的方法來覆蓋。

某些場合下,只靠純粹的介面不能滿足類與類之間的協調,還必需類中表示狀態的變數來區別不同的關係。abstract的中介作用可以很好地滿足這一點。

規範了一組相互協調的方法,其中一些方法是共同的,與狀態無關的,可以共享的,無需子類分別實現;而另一些方法卻需要各個子類根據自己特定的狀態來實現特定的功能

底層原始碼實現相關:

  1. Map

HashMap的工作原理

HashMap基於hash原理,我們通過put()和get()方法儲存和獲取物件。當我們將鍵值對傳遞給put()方法時,它呼叫鍵物件的hashCode()方法來計算hashcode,讓後找到bucket位置來儲存值物件。當獲取物件時,通過鍵物件的equals()方法找到正確的鍵值對,然後返回值物件。HashMap使用連結串列來解決碰撞問題,當發生碰撞了,物件將會儲存在連結串列的下一個節點中。 HashMap在每個連結串列節點中儲存鍵值對物件。

HashMap擴容原理:

預設初始化容量16;增長因子0.75

Hash演算法本質上就是三步:取key的hashCode值、高位運算、取模運算

①.判斷鍵值對陣列table[i]是否為空或為null,否則執行resize()進行擴容;

②.根據鍵值key計算hash值得到插入的陣列索引i,如果table[i]==null,直接新建節點新增,轉向⑥,如果table[i]不為空,轉向③;

③.判斷table[i]的首個元素是否和key一樣,如果相同直接覆蓋value,否則轉向④,這裡的相同指的是hashCode以及equals;

④.判斷table[i] 是否為treeNode,即table[i] 是否是紅黑樹,如果是紅黑樹,則直接在樹中插入鍵值對,否則轉向⑤;

⑤.遍歷table[i],判斷連結串列長度是否大於8,大於8的話把連結串列轉換為紅黑樹,在紅黑樹中執行插入操作,否則進行連結串列的插入操作;遍歷過程中若發現key已經存在直接覆蓋value即可;

⑥.插入成功後,判斷實際存在的鍵值對數量size是否超多了最大容量threshold,如果超過,進行擴容。

當兩個不同的鍵物件的hashcode相同時會發生什麼?

它們會儲存在同一個bucket位置的連結串列中。鍵物件的equals()方法用來找到鍵值對。

 

Hash碰撞產生及解決:

   Hashmap裡面的bucket出現了單連結串列的形式,雜湊表要解決的一個問題就是雜湊值的衝突問題,通常是兩種方法:連結串列法和開放地址法。連結串列法就是將相同hash值的物件組織成一個連結串列放在hash值對應的槽位;開放地址法是通過一個探測演算法,當某個槽位已經被佔據的情況下繼續查詢下一個可以使用的槽位。

HashMap無序

* HashMap是最常用的Map,它根據鍵的HashCode值儲存資料,根據鍵可以直接獲取它的值,具有很快的訪問速度,遍歷時,取得資料的順序是完全隨機的。因為鍵物件不可以重複,所以HashMap最多隻允許一條記錄的鍵為Null,允許多條記錄的值為Null,是非同步的

JDK1.8中,HashMap採用陣列+連結串列+紅黑樹實現,當連結串列長度超過閾值(8)時,並且陣列總容量超過64時,將連結串列轉換為紅黑樹,這樣大大減少了查詢時間。從連結串列轉換為紅黑樹後增加的時候效率低點,查詢、刪除的效率都高。

JDK1.8使用紅黑樹的改進

  在java jdk8中對HashMap的原始碼進行了優化,在jdk7中,HashMap處理“碰撞”的時候,都是採用連結串列來儲存,當碰撞的結點很多時,查詢時間是O(n)。

  在jdk8中,HashMap處理“碰撞”增加了紅黑樹這種資料結構,當碰撞結點較少時,採用連結串列儲存,當較大時(>8個),採用紅黑樹(特點是查詢時間是O(logn))儲存(有一個閥值控制,大於閥值(8個),將連結串列儲存轉換成紅黑樹儲存)

HashTable

 

* Hashtable與HashMap類似,是HashMap的執行緒安全版,它支援執行緒的同步,即任一時刻只有一個執行緒能寫Hashtable,因此也導致了Hashtale在寫入時會比較慢,它繼承自Dictionary類,不同的是它不允許記錄的鍵或者值為null,同時效率較低。

LinkedHashMap有序

LinkedHashMap儲存了記錄的插入順序,在用Iteraor遍歷LinkedHashMap時,先得到的記錄肯定是先插入的,在遍歷的時候會比HashMap慢,有HashMap的全部特性。

LinkedHashMap還使用了一個雙向連結串列實現順序存取,這個雙向連結串列的實現依賴於Entry這個內部類,這個Entry內部類在集合中非常常見,在刪除和增加時,都在修改前面的引用和後面的引用。LinkedHashMap中還用到了連結串列記錄順序,在LinkedHashMap中並沒有put方法,而是利用了HashMap中的put方法,但是重寫了put方法中呼叫的的addEntry()方法,在新增節點的時候(由於是雙向連結串列)都會在尾部進行新增

TreeMap

* TreeMap實現SortMap介面,能夠把它儲存的記錄根據鍵排序,預設是按鍵值的升序排序(自然順序),也可以指定排序的比較器,當用Iterator遍歷TreeMap時,得到的記錄是排過序的。不允許key值為空,非同步的

ConcurrentHashMap

* 執行緒安全,並且鎖分離。ConcurrentHashMap內部使用段(Segment)來表示這些不同的部分,每個段其實就是一個小的hash table,它們有自己的鎖。只要多個修改操作發生在不同的段上,它們就可以併發進行。

  1. List

*(List擴容倍數1.5倍初始長度10)

ArrayList  (執行緒不安全

LinkedList (執行緒不安全,適合增刪操作較多情況

* ArrayList是實現了基於動態陣列的資料結構,LinkedList基於連結串列的資料結構。

* 對於隨機訪問get和set,ArrayList覺得優於LinkedList,因為LinkedList要移動指標。

* 對於新增和刪除操作add和remove,LinkedList比較佔優勢,因為ArrayList要移動資料。 這一點要看實際情況的。若只對單條資料插入或刪除,ArrayList的速度反而優於LinkedList。但若是批量隨機的插入刪除資料,LinkedList的速度大大優於ArrayList. 因為ArrayList每插入一條資料,要移動插入點及之後的所有資料。

Vector(執行緒安全,執行緒同步的

* vector是執行緒同步的,所以它也是執行緒安全的,而arraylist是執行緒非同步的,是不安全的。如果不考慮到執行緒的安全因素,一般用arraylist效率比較高。

List 擴容原理

java自動增加ArrayList大小的思路是:向ArrayList新增物件時,原物件數目加1如果大於原底層陣列長度,則以適當長度新建一個原陣列的拷貝,並修改原陣列,指向這個新建陣列。原陣列自動拋棄(java垃圾回收機制會自動回收)。size則在向陣列新增物件,自增1

ArrayList追加物件時,Java總是要先計算容量(Capacity)是否適當,若容量不足則把原陣列拷貝到以指定容量為長度建立的 新陣列內,並對原陣列變數重新賦值,指向新陣列。在這同時,size進行自增1。

刪除物件時,先使用拷貝方法把指定index後面的物件前移1位(如果 有的話),然後把空出來的位置置null,交給Junk收集器銷燬,size自減1,即完成了。

Java 陣列和連結串列的區別以及使用場景

陣列:是將元素在記憶體中連續儲存的;它的優點:因為資料是連續儲存的,記憶體地址連續,所以在查詢資料的時候效率比較高;它的缺點:在儲存之前,我們需要申請一塊連續的記憶體空間,並且在編譯的時候就必須確定好它的空間的大小。在執行的時候空間的大小是無法隨著你的需要進行增加和減少而改變的,當資料兩比較大的時候,有可能會出現越界的情況,資料比較小的時候,又有可能會浪費掉記憶體空間。在改變資料個數時,增加、插入、刪除資料效率比較低。

連結串列:是動態申請記憶體空間,不需要像陣列需要提前申請好記憶體的大小,連結串列只需在用的時候申請就可以,根據需要來動態申請或者刪除記憶體空間,對於資料增加和刪除以及插入比陣列靈活。還有就是連結串列中資料在記憶體中可以在任意的位置,通過應用來關聯資料(就是通過存在元素的指標來聯絡)。

陣列和連結串列就拿增加資料來說,陣列中增加一個元素,需要移動大量的元素,在記憶體中空出一個元素的空間,然後將增加的元素放到空出的空間中;而連結串列就是將連結串列中最後的一個元素的指標指向新增的元素,在指出新增元素是尾元素就好了。

陣列應用場景:

1、資料比較少;

2、經常做的運算是按序號訪問資料元素;

3、陣列更容易實現,任何高階語言都支援;

4、構建的線性表較穩定。

連結串列應用場景:

1、對線性表的長度或者規模難以估計;

2、可以頻繁做插入刪除操作;

3、構建動態性比較強的線性表。

陣列和連結串列對比

分類

陣列

連結串列

邏輯結構角度

陣列必須事先定義固定的長度(元素個數),不能適應資料動態地增減的情況。當資料增加時,可能超出原先定義的元素個數;當資料減少時,造成記憶體浪費

連結串列動態地進行儲存分配,可以適應資料動態地增減的情況,且可以方便地插入、刪除資料項。(陣列中插入、刪除資料項時,需要移動其它資料項)

記憶體儲存角度

(靜態)陣列從棧中分配空間, 對於程式設計師方便快速,但自由度小

連結串列從堆中分配空間, 自由度大但申請管理比較麻煩

 

陣列連結串列對比總結

  1. 陣列靜態分配記憶體,連結串列動態分配記憶體;
  2. 陣列在記憶體中連續,連結串列不連續;
  3. 陣列元素在棧區,連結串列元素在堆區;
  4. 陣列利用下標定位,時間複雜度為O(1),連結串列定位元素時間複雜度O(n);
  5. 陣列插入或刪除元素的時間複雜度O(n),連結串列的時間複雜度O(1);

資料庫相關:

  1. 資料庫索引

一、為什麼要建立索引呢(優點)?

這是因為,建立索引可以大大提高系統的效能。

第一,   通過建立唯一性索引,可以保證資料庫表中每一行資料的唯一性。

第二,   可以大大加快資料的檢索速度,這也是建立索引的最主要的原因。

第三,   可以加速表和表之間的連線,特別是在實現資料的參考完整性方面特別有意義。

第四,   在使用分組和排序子句進行資料檢索時,同樣可以顯著減少查詢中分組和排序的時間。

第五,   通過使用索引,可以在查詢的過程中,使用優化隱藏器,提高系統的效能。

二、建立方向索引的不利因素(缺點

也許會有人要問:增加索引有如此多的優點,為什麼不對錶中的每一個列建立一個索引呢?這種想法固然有其合理性,然而也有其片面性。雖然,索引有許多優點,但是,為表中的每一個列都增加索引,是非常不明智的。這是因為,增加索引也有許多不利的一個方面。

第一,   建立索引和維護索引要耗費時間,這種時間隨著資料量的增加而增加。

第二,   索引需要佔物理空間,除了資料表佔資料空間之外,每一個索引還要佔一定的物理空間,如果要建立聚簇索引,那麼需要的空間就會更大。

第三,   當對錶中的資料進行增加、刪除和修改的時候,索引也要動態的維護,這樣就降低了資料的維護速度。 

mysql

MySQL是怎麼保證主備一致的?

 binlog 用來做主備同步

binlog 的三種格式對比

 

statement, row,mixed,mixed其實它就是前兩種格式的混合。

binlog_format=statement 時,binlog 裡面記錄的就是 SQL 語句的原文;

當 binlog_format 使用 row 格式的時候,binlog 裡面記錄了真實刪除行的主鍵 id,這樣 binlog 傳到備庫去的時候,就肯定會刪除 id=4 的行,不會有主備刪除不同行的問題。

mixed 格式的意思是,MySQL 自己會判斷這條 SQL 語句是否可能引起主備不一致,如果有可能,就用 row 格式,否則就用 statement 格式。

mysql執行過程:

執行 explain 來查詢索引是否生效

使用方法,在select語句前加上explain就可以了:

EXPLAIN SELECT surname,first_name form a,b WHERE a.id=b.id

EXPLAIN命令結果的每一列進行說明:

  1. select_type:表示SELECT的型別,常見的取值有:

型別

說明

SIMPLE

簡單表,不使用表連線或子查詢

PRIMARY

主查詢,即外層的查詢

UNION

UNION中的第二個或者後面的查詢語句

SUBQUERY

子查詢中的第一個

  1. table:輸出結果集的表(表別名)
  2. type:表示MySQL在表中找到所需行的方式,或者叫訪問型別。常見訪問型別如下,從上到下,效能由差到最好:

ALL

全表掃描

index

索引全掃描

range

索引範圍掃描

ref

非唯一索引掃描

eq_ref

唯一索引掃描

const,system

單表最多有一個匹配行

NULL

不用掃描表或索引

    1. type=ALL,全表掃描,MySQL遍歷全表來找到匹配行

一般是沒有where條件或者where條件沒有使用索引的查詢語句

EXPLAIN SELECT * FROM customer WHERE active=0;

 

    1. type=index,索引全掃描,MySQL遍歷整個索引來查詢匹配行,並不會掃描表

一般是查詢的欄位都有索引的查詢語句

EXPLAIN SELECT store_id FROM customer;

    1. type=range,索引範圍掃描,常用於<、<=、>、>=、between等操作

EXPLAIN SELECT * FROM customer WHERE customer_id>=10 AND customer_id<=20;

注意這種情況下比較的欄位是需要加索引的,如果沒有索引,則MySQL會進行全表掃描,如下面這種情況,create_date欄位沒有加索引:

EXPLAIN SELECT * FROM customer WHERE create_date>='2006-02-13' ;

    1. type=ref,使用非唯一索引或唯一索引的字首掃描,返回匹配某個單獨值的記錄行

store_id欄位存在普通索引(非唯一索引)

EXPLAIN SELECT * FROM customer WHERE store_id=10;

 

ref型別還經常會出現在join操作中:

customerpayment表關聯查詢,關聯欄位customer.customer_id(主鍵),payment.customer_id(非唯一索引)。表關聯查詢時必定會有一張表進行全表掃描,此表一定是幾張表中記錄行數最少的表,然後再通過非唯一索引尋找其他關聯表中的匹配行,以此達到表關聯時掃描行數最少。

因為customerpayment兩表中customer表的記錄行數最少,所以customer表進行全表掃描,payment表通過非唯一索引尋找匹配行。

EXPLAIN SELECT * FROM customer customer INNER JOIN payment payment ON customer.customer_id = payment.customer_id;

    1. type=eq_ref,類似ref,區別在於使用的索引是唯一索引,對於每個索引鍵值,表中只有一條記錄匹配

eq_ref一般出現在多表連線時使用primary key或者unique index作為關聯條件。

 

film、film_text表關聯查詢和上一條所說的基本一致,只不過關聯條件由非唯一索引變成了主鍵。

EXPLAIN SELECT * FROM film film INNER JOIN film_text film_text ON film.film_id = film_text.film_id;

    1. type=const/system,單表中最多有一條匹配行,查詢起來非常迅速,所以這個匹配行的其他列的值可以被優化器在當前查詢中當作常量來處理

const/system出現在根據主鍵primary key或者 唯一索引 unique index 進行的查詢

根據主鍵primary key進行的查詢:

EXPLAIN SELECT * FROM customer WHERE customer_id =10;

根據唯一索引unique index進行的查詢:

EXPLAIN SELECT * FROM customer WHERE email ='MARY.SMITH@sakilacustomer.org';

    1. type=NULL,MySQL不用訪問表或者索引,直接就能夠得到結果

  1. possible_keys: 表示查詢可能使用的索引
  2. key: 實際使用的索引
  3. key_len: 使用索引欄位的長度
  4. ref: 使用哪個列或常數與key一起從表中選擇行。
  5. rows: 掃描行的數量
  6. filtered: 儲存引擎返回的資料在server層過濾後,剩下多少滿足查詢的記錄數量的比例(百分比)
  7. Extra: 執行情況的說明和描述,包含不適合在其他列中顯示但是對執行計劃非常重要的額外資訊

最主要的有一下三種:

Using Index

表示索引覆蓋,不會回表查詢

Using Where

表示進行了回表查詢

Using Index Condition

表示進行了ICP優化

Using Flesort

表示MySQL需額外排序操作, 不能通過索引順序達到排序效果

索引失效的幾種情況:

  1. 當語句中帶有or的時候 即使有索引也會失效。
  2. 當語句索引 like 帶%的時候索引失效(注意:如果上句為 like‘xzz’此時索引是生效的) 
  3. 如果列型別是字串,那一定要在條件中將資料使用引號引用起來,否則不使用索引
  4. mysql聯合所以有最左原則
  5. 索引失效,不要在索引上進行操作,否則索引會失效(是有類似時間轉換的問題和上訴問題一樣)select * from USER where age-1>11;
  6. 列資料欄位值為Null 索引失效

MySQL索引主要有兩種結構:

Hash索引和B+ Tree索引,我們使用的是InnoDB引擎,預設的是B+

B+ Tree索引和Hash索引區別 :

雜湊索引適合等值查詢,但是不無法進行範圍查詢 雜湊索引沒辦法利用索引完成排序 雜湊索引不支援多列聯合索引的最左匹配規則 如果有大量重複鍵值得情況下,雜湊索引的效率會很低,因為存在雜湊碰撞問題

Mysql 大資料分頁:

  1. 利用子查詢優化分頁查詢

以上分頁查詢的問題在於,我們查詢獲取的 10020 行資料結果都返回給我們了,我們能否先查詢出所需要的 20 行資料中的最小 ID 值,然後通過偏移量返回所需要的 20 行資料給我們呢?我們可以通過索引覆蓋掃描,使用子查詢的方式來實現分頁查詢:

 

select * from `demo`.`order` where id> (select id from `demo`.`order` order by order_no limit 10000, 1) limit 20;

 

 

MYSQL資料庫引擎 MYISAM和 INNODB區別:

1、 儲存結構

MyISAM:每個MyISAM在磁碟上儲存成三個檔案。第一個檔案的名字以表的名字開始,副檔名指出檔案型別。.frm檔案儲存表定義。資料檔案的副檔名為.MYD (MYData)。索引檔案的副檔名是.MYI (MYIndex)。

InnoDB:所有的表都儲存在同一個資料檔案中(也可能是多個檔案,或者是獨立的表空間檔案),InnoDB表的大小隻受限於作業系統檔案的大小,一般為2GB。

2、 儲存空間

MyISAM:可被壓縮,儲存空間較小。支援三種不同的儲存格式:靜態表(預設,但是注意資料末尾不能有空格,會被去掉)、動態表、壓縮表。

InnoDB:需要更多的記憶體和儲存,它會在主記憶體中建立其專用的緩衝池用於高速緩衝資料和索引。

3、 事務支援

MyISAM:強調的是效能,每次查詢具有原子性,其執行數度比InnoDB型別更快,但是不提供事務支援。

InnoDB:提供事務支援事務,外部鍵等高階資料庫功能。 具有事務(commit)、回滾(rollback)和崩潰修復能力(crash recovery capabilities)的事務安全(transaction-safe (ACID compliant))型表。

4、 CURD操作

MyISAM:如果執行大量的SELECT,MyISAM是更好的選擇。(因為沒有支援行級鎖),在增刪的時候需要鎖定整個表格,效率會低一些。相關的是innodb支援行級鎖,刪除插入的時候只需要鎖定改行就行,效率較高

InnoDB:如果你的資料執行大量的INSERT或UPDATE,出於效能方面的考慮,應該使用InnoDB表。DELETE 從效能上InnoDB更優,但DELETE FROM table時,InnoDB不會重新建立表,而是一行一行的刪除,在innodb上如果要清空儲存有大量資料的表,最好使用truncate table這個命令。

5、 外來鍵

MyISAM:不支援

InnoDB:支援

資料庫ACID

原子性(Atomicity)

  原子性是指事務包含的所有操作要麼全部成功,要麼全部失敗回滾,這和前面兩篇部落格介紹事務的功能是一樣的概念,因此事務的操作如果成功就必須要完全應用到資料庫,如果操作失敗則不能對資料庫有任何影響。

一致性(Consistency)

  一致性是指事務必須使資料庫從一個一致性狀態變換到另一個一致性狀態,也就是說一個事務執行之前和執行之後都必須處於一致性狀態。

  拿轉賬來說,假設使用者A和使用者B兩者的錢加起來一共是5000,那麼不管A和B之間如何轉賬,轉幾次賬,事務結束後兩個使用者的錢相加起來應該還得是5000,這就是事務的一致性。

隔離性(Isolation)

  隔離性是當多個使用者併發訪問資料庫時,比如操作同一張表時,資料庫為每一個使用者開啟的事務,不能被其他事務的操作所干擾,多個併發事務之間要相互隔離。

  即要達到這麼一種效果:對於任意兩個併發的事務T1和T2,在事務T1看來,T2要麼在T1開始之前就已經結束,要麼在T1結束之後才開始,這樣每個事務都感覺不到有其他事務在併發地執行。

永續性(Durability)

  永續性是指一個事務一旦被提交了,那麼對資料庫中的資料的改變就是永久性的,即便是在資料庫系統遇到故障的情況下也不會丟失提交事務的操作。

  例如我們在使用JDBC運算元據庫時,在提交事務方法後,提示使用者事務操作完成,當我們程式執行完成直到看到提示後,就可以認定事務以及正確提交,即使這時候資料庫出現了問題,也必須要將我們的事務完全執行完成,否則就會造成我們看到提示事務處理完畢,但是資料庫因為故障而沒有執行事務的重大錯誤。

髒讀  髒讀是指在一個事務處理過程裡讀取了另一個未提交的事務中的資料。   當一個事務正在多次修改某個資料,而在這個事務中這多次的修改都還未提交,這時一個併發的事務來訪問該資料,就會造成兩個事務得到的資料不一致。

  1. mybatis

MyBatis $和#的區別

#{xxx},使用的是PreparedStatement,會有型別轉換,所以比較安全;

${xxx},使用字串拼接,可以SQL隱碼攻擊;

mybatis用sql預編譯的;其實框架底層使用的正是PreparedStatement類。PreparedStaement類不但能夠避免SQL隱碼攻擊,因為已經預編譯,當N次執行同一條sql語句時,節約了(N-1)次的編譯時間,從而能夠提高效率。

MyBatis 查詢快取來快取

MyBatis 的快取分為一級快取二級快取

  1. 一級快取是 SqlSession 級別的快取
  2. 二級快取是 mapper 級別的快取,多個 SqlSession 共享

一級快取

一級快取是 SqlSession 級別的快取,是基於 HashMap 的本地快取。不同的 SqlSession 之間的快取資料區域互不影響。

一級快取的作用域是 SqlSession 範圍,當同一個 SqlSession 執行兩次相同的 sql 語句時,第一次執行完後會將資料庫中查詢的資料寫到快取,第二次查詢時直接從快取獲取不用去資料庫查詢。當 SqlSession 執行 insert、update、delete 操做並提交到資料庫時,會清空快取,保證快取中的資訊是最新的。

MyBatis 預設開啟一級快取。

二級快取

二級快取是 mapper 級別的快取,同樣是基於 HashMap 進行儲存,多個 SqlSession 可以共用二級快取,其作用域是 mapper 的同一個 namespace。不同的 SqlSession 兩次執行相同的 namespace 下的 sql 語句,會執行相同的 sql,第二次查詢只會查詢第一次查詢時讀取資料庫後寫到快取的資料,不會再去資料庫查詢。

MyBatis 是如何載入Dao的?

1.手動程式碼方式:使用Mybatis提供的API進行操作,通過獲取SqlSession物件,然後根據Statement Id 和引數來運算元據庫。

2.使用註解自動方式: MapperScannerConfigurer動態的註冊Bean資訊

Mapper介面的代理建立過程:

  1. 掃描mapper介面基本包,將為註冊為BeanDefinition物件。
  2. 設定BeanDefinition的物件的beanClass和sqlSessionFactory屬性。
  3. 設定sqlSessionFactory屬性的時候,呼叫SqlSessionTemplate的構造方法,建立SqlSession介面的代理類。
  4. 獲取BeanDefinition物件的時候,呼叫其工廠方法getObject,返回mapper介面的代理類。

Spring相關:

Spring Bean 的例項化過程(生命週期)

  1. 例項化容器(ApplicationContext介面負責例項化,配置和組裝 bean
  2. 初始化Bean(InitializingBean、PostConstruct、init-method

  1. 首先@PostConstruct 會被最先呼叫
  2. 其次 InitializingBean.afterPropertiesSet() 方法將會被呼叫
  3. 最後呼叫通過 XML 配置的 init-method 方法或通過設定 @Bean 註解 設定 initMethod 屬性的方法
  1. 後置處理器處理(BeanPostProcessor

Spring MVC的設計是圍繞DispatcherServlet展開的,DispatcherServlet負責將請求派發到特定的handler。通過可配置的handler mappings、view resolution、locale以及theme resolution來處理請求並且轉到對應的檢視。

DispatcherServlet 核心流程:

一個特定的 DispatcherServlet 請求過來之後,它的工作流程如下:

(1)WebApplicationContext 上下文尋求並繫結控制器和 ,預設情況下會繫結 DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE下的預設值。

(2)語言解析器繫結到請求啟動過程中的元素來解決所使用的語言環境處理請求(渲染檢視、準備資料等),如果不需要處理國際化,就不需要這步。

(3)主題解析器繫結請求讓檢視這樣的元素知道需要使用哪種主題,如果沒有使用主題,同樣忽略此步驟。

(4)如果指定一個多檔案解析器,請求就會檢查這些檔案,如果找到這些檔案,請求就會被包裝到 MultipartHttpServletRequest中,進一步處理其他元素。

(5)接下來,尋找一個合適的處理器,如果找到了,處理器相關執行鏈就會執行,為資料模型或渲染檢視做準備。

(6)如果返回了模型,就會渲染檢視。如果沒有返回模型(可能是為了安全考慮,預處理或後處理程式攔截了請求),就不需要渲染檢視,請求可能已經完成了。

事務的傳播屬性(Propagation) 

1) REQUIRED ,這個是預設的屬性 

Support a current transaction, create a new one if none exists. 

如果存在一個事務,則支援當前事務。如果沒有事務則開啟一個新的事務。 

被設定成這個級別時,會為每一個被呼叫的方法建立一個邏輯事務域。如果前面的方法已經建立了事務,那麼後面的方法支援當前的事務,如果當前沒有事務會重新建立事務。 

如圖所示: 

2) MANDATORY 

Support a current transaction, throw an exception if none exists.支援當前事務,如果當前沒有事務,就丟擲異常。 

3) NEVER 

Execute non-transactionally, throw an exception if a transaction exists. 

以非事務方式執行,如果當前存在事務,則丟擲異常。 

4) NOT_SUPPORTED 

Execute non-transactionally, suspend the current transaction if one exists. 

以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。 

5) REQUIRES_NEW 

Create a new transaction, suspend the current transaction if one exists. 

新建事務,如果當前存在事務,把當前事務掛起。 

6) SUPPORTS 

Support a current transaction, execute non-transactionally if none exists. 

支援當前事務,如果當前沒有事務,就以非事務方式執行。 

7) NESTED 

Execute within a nested transaction if a current transaction exists, behave like PROPAGATION_REQUIRED else. 

支援當前事務,新增Savepoint點,與當前事務同步提交或回滾。 

巢狀事務一個非常重要的概念就是內層事務依賴於外層事務。外層事務失敗時,會回滾內層事務所做的動作。而內層事務操作失敗並不會引起外層事務的回滾。 

8) PROPAGATION_NESTED 與PROPAGATION_REQUIRES_NEW的區別 

它們非常 類似,都像一個巢狀事務,如果不存在一個活動的事務,都會開啟一個新的事務。使用PROPAGATION_REQUIRES_NEW時,內層事務與外層事務就像兩個獨立的事務一樣,一旦內層事務進行了提交後,外層事務不能對其進行回滾。兩個事務互不影響。兩個事務不是一個真正的巢狀事務。同時它需要JTA 事務管理器的支援。 

使用PROPAGATION_NESTED時,外層事務的回滾可以引起內層事務的回滾。而內層事務的異常並不會導致外層事務的回滾,它是一個真正的巢狀事務。 

 事務的隔離級別(Isolation Level) 

i. Dirty Reads 髒讀 

一個事務正在對資料進行更新操作,但是更新還未提交,另一個事務這時也來操作這組資料,並且讀取了前一個事務還未提交的資料,而前一個事務如果操作失敗進行了回滾,後一個事務讀取的就是錯誤資料,這樣就造成了髒讀。

ii. Non-Repeatable Reads 不可重複讀 

一個事務多次讀取同一資料,在該事務還未結束時,另一個事務也對該資料進行了操作,而且在第一個事務兩次次讀取之間,第二個事務對資料進行了更新,那麼第一個事務前後兩次讀取到的資料是不同的,這樣就造成了不可重複讀。

iii. Phantom Reads 幻像讀 

第一個資料正在查詢符合某一條件的資料,這時,另一個事務又插入了一條符合條件的資料,第一個事務在第二次查詢符合同一條件的資料時,發現多了一條前一次查詢時沒有的資料,彷彿幻覺一樣,這就是幻像讀。

iv. 非重複度和幻像讀的區別 

非重複讀是指同一查詢在同一事務中多次進行,由於其他提交事務所做的修改或刪除,每次返回不同的結果集,此時發生非重複讀。(A transaction rereads data it has previously read and finds that another committed transaction has modified or deleted the data. )

幻像讀是指同一查詢在同一事務中多次進行,由於其他提交事務所做的插入操作,每次返回不同的結果集,此時發生幻像讀。(A transaction reexecutes a query returning a set of rows that satisfies a search condition and finds that another committed transaction has inserted additional rows that satisfy the condition. )

表面上看,區別就在於非重複讀能看見其他事務提交的修改和刪除,而幻像能看見其他事務提交的插入。 

 巢狀事物

  可能大家對PROPAGATION_NESTED還不怎麼了解,覺得有必要再補充一下^_^!

PROPAGATION_NESTED: 巢狀事務型別,是相對上面提到的六種情況(上面的六種應該稱為平面事務型別),打個比方我現在有一個事務主要有一下幾部分:

      1,從A使用者帳戶裡面減去100元錢

      2,往B使用者帳戶裡面新增100元錢

       這樣看和以前不同的事務可能沒有什麼區別,那我現在有點特殊的要求就是,A使用者有3個帳戶,B使用者有2個帳戶,現在我的要求就是隻要再A使用者的3個帳戶裡面任意一個減去100元,往B使用者的兩個帳戶中任意一個裡面增加100元就可以了!

       一旦你有這樣的要求那巢狀事務型別就非常適合你!我們可以這樣理解,

       一:將“從A使用者帳戶裡面減去100元錢” 和 “往B使用者帳戶裡面增加100元錢”我們暫時認為是一級事務操作

       二:將從A使用者的3個帳戶的任意一個帳戶裡面減錢看做是“從A使用者帳戶裡面減去100元錢”這個一級事務的子事務(二級事務),同樣把後面存錢的看成是另一個的二級事務。

      問題一:當二級事務被rollback一級事務會不會被rollback?

      答案是不會的,二級事務的rollback只針對自己。

      問題二:什麼時候這個一級事務會commit,什麼時候會被rollback呢?

      我們主要看二級裡面出現的情況,當所有的二級事務被commit了並且一級事務沒有失敗的操作,那整個事務就算是一個成功的事務,這種情況整個事務會被commit。

當任意一個二級事務沒有被commit那整個事務就是失敗的,整個事務會被roolback。

還是拿上面的例子來說明吧!如果我在a的三個帳戶裡面減錢的操作都被二級事務給rollback了,也就是3個帳戶裡面都沒有減錢成功,整個事務就失敗了就會被rollback。如果A使用者帳戶三個帳戶裡面有一個可以扣錢而且B使用者的兩個帳戶裡面也有一個帳戶可以增加錢,那整個事務就算成功的,會被 commit。

看了一下覺得上面的例子好像不是很深刻,看這個情況(A使用者的3個帳戶都是有信用額度的,也就是說可以超支,但是超支有金額限制)。

  1. Spring AOP  IOC具體的理解

AOP--Aspect Oriented Programming面向切面程式設計;用來封裝橫切關注點,具體可以在下面的場景中使用:

Authentication 許可權、Caching 快取、Context passing 內容傳遞、Error handling 錯誤處理Lazy loading懶載入、Debugging除錯、logging, tracing, profiling and monitoring 記錄跟蹤優化 校準、Performance optimization 效能優化、Persistence 持久化、Resource pooling 資源池、Synchronization 同步、Transactions 事務

原理:AOP是面向切面程式設計,是通過動態代理的方式為程式新增統一功能,集中解決一些公共問題。

優點:1.各個步驟之間的良好隔離性耦合性大大降低 

           2.原始碼無關性,再擴充套件功能的同時不對原始碼進行修改操作 

IOC--Inversion of Control控制反轉當某個角色需要另外一個角色協助的時候,在傳統的程式設計過程中,通常由呼叫者來建立被呼叫者的例項物件。但在spring中建立被呼叫者的工作不再由呼叫者來完成,因此稱為控制反轉。建立被呼叫者的工作由spring來完成,然後注入呼叫者 直接使用。

原理:Spring 通過一個配置檔案描述 Bean 及 Bean 之間的依賴關係,利用 Java 語言的反射功能例項化 Bean 並建立 Bean 之間的依賴關係。 Spring 的 IoC 容器在完成這些底層工作的基礎上,還提供了 Bean 例項快取、生命週期管理、 Bean 例項代理、事件釋出、資源裝載等高階服務。

Java動態代理的實現方式有幾種?

JDK 自身提供的動態代理,就是主要利用了上面提到的反射機制。

還有其他的實現方式,比如利用傳說中更高效能的位元組碼操作機制,類似 ASM、cglib(基於 ASM)、Javassist 等。

BeanFactory 和 ApplicationContext 的不同點:

BeanFactory 介面

這是一個用來訪問 Spring 容器的 root 介面,要訪問 Spring 容器,我們將使用 Spring 依賴注入功能,使用 BeanFactory 介面和它的子介面

特性:

  1. Bean 的例項化/串聯

ApplicationContext 介面

ApplicationContext 是 Spring 應用程式中的中央介面,用於嚮應用程式提供配置資訊繼承了 BeanFactory 介面,所以 ApplicationContext 包含 BeanFactory 的所有功能以及更多功能!它的主要功能是支援大型的業務應用的建立

特性:

  1. Bean instantiation/wiring
  2. Bean 的例項化/串聯
  3. 自動的 BeanPostProcessor 註冊
  4. 自動的 BeanFactoryPostProcessor 註冊
  5. 方便的 MessageSource 訪問(i18n)
  6. ApplicationEvent 的釋出

與 BeanFactory 懶載入的方式不同,它是預載入,所以,每一個 bean 都在 ApplicationContext 啟動之後例項化

BeanFactory和ApplicationContext有什麼區別?

BeanFactory 可以理解為含有bean集合的工廠類。BeanFactory 包含了種bean的定義,以便在接收到客戶端請求時將對應的bean例項化。

BeanFactory還能在例項化物件的時生成協作類之間的關係。此舉將bean自身與bean客戶端的配置中解放出來。BeanFactory還包含了bean生命週期的控制,呼叫客戶端的初始化方法(initialization methods)和銷燬方法(destruction methods)。

從表面上看,application context如同bean factory一樣具有bean定義、bean關聯關係的設定,根據請求分發bean的功能。但application context在此基礎上還提供了其他的功能。

  1. 提供了支援國際化的文字訊息
  2. 統一的資原始檔讀取方式
  3. 已在監聽器中註冊的bean的事件

以下是三種較常見的 ApplicationContext 實現方式:

1、ClassPathXmlApplicationContext:從classpath的XML配置檔案中讀取上下文,並生成上下文定義。應用程式上下文從程式環境變數中取得。

2、FileSystemXmlApplicationContext :由檔案系統中的XML配置檔案讀取上下文。

3、XmlWebApplicationContext:由Web應用的XML檔案讀取上下文。

  1. Spring框架中的單例Beans是執行緒安全的麼?

Spring框架並沒有對單例bean進行任何多執行緒的封裝處理。關於單例bean的執行緒安全和併發問題需要開發者自行去搞定。但實際上,大部分的Spring bean並沒有可變的狀態(比如Serview類和DAO類),所以在某種程度上說Spring的單例bean是執行緒安全的。如果你的bean有多種狀態的話(比如 View Model 物件),就需要自行保證執行緒安全。

最淺顯的解決辦法就是將多型bean的作用域由“singleton”變更為“prototype”。

Spring迴圈依賴怎麼解決?

手動獲取bean

多執行緒相關:

  1. 介紹下Java JUC和常用的介面(java.util.concurrent併發工具包)

併發程式設計中很常用的實用工具類,用於定義類似於執行緒的自定義子系統,包括執行緒池、非同步IO 和輕量級任務框架。提供可調的、靈活的執行緒池。還提供了設計用於多執行緒上下文中的Collection 實現等:

Callable.class

ConcurrentHashMap.class

Executor.class

ExecutorService.class

Future.class

FutureTask.class

  1. 同步執行緒的幾種方式

1、使用synchronized關鍵字同步方法、同步類。(由於java的每個物件都有一個內建鎖,當用此關鍵字修飾方法時, 內建鎖會保護整個方法。在呼叫該方法前,需要獲得內建鎖,否則就處於阻塞狀態。 注:同步是一種高開銷的操作,因此應該儘量減少同步的內容。通常沒有必要同步整個方法,使用synchronized程式碼塊同步關鍵程式碼即可。 )

2wait與notify 只能作用於同步執行緒中

wait():使一個執行緒處於等待狀態,並且釋放所持有的物件的lock。

sleep():使一個正在執行的執行緒處於睡眠狀態,是一個靜態方法,呼叫此方法要捕捉InterruptedException異常。

notify():喚醒一個處於等待狀態的執行緒,注意的是在呼叫此方法的時候,並不能確切的喚醒某一個等待狀態的執行緒,而是由JVM確定喚醒哪個執行緒,而且不是按優先順序。

Allnotity():喚醒所有處入等待狀態的執行緒,注意並不是給所有喚醒執行緒一個物件的鎖,而是讓它們競爭。

3使用特殊域變數(volatile)實現執行緒同步

    a.volatile關鍵字為域變數的訪問提供了一種免鎖機制

    b.使用volatile修飾域相當於告訴虛擬機器該域可能會被其他執行緒更新

    c.因此每次使用該域就要重新計算,而不是使用暫存器中的值 

    d.volatile不會提供任何原子操作,它也不能用來修飾final型別的變數 

4使用重入鎖實現執行緒同步java.util.concurrent.ReentrantLock來支援同步

 ReentrantLock類是可重入、互斥、實現了Lock介面的鎖,它與使用synchronized方法和快具有相同的基本行為和語義,並且擴充套件了其能力。

 ReenreantLock類的常用方法有:

ReentrantLock() : 建立一個ReentrantLock例項  lock() : 獲得鎖  unlock() : 釋放鎖

lock鎖中的執行緒之間通訊 使用

注:ReentrantLock()還有一個可以建立公平鎖的構造方法,但由於能大幅度降低程式執行效率,不推薦使用 

5、使用區域性變數實現執行緒同步ThreadLocal管理變數,則每一個使用該變數的執行緒都獲得該變數的副本,副本之間相互獨立,這樣每一個執行緒都可以隨意修改自己的變數副本,而不會對其他執行緒產生影響。

執行緒池相關:

ThreadPoolExecutor

使用ThreadPoolExecutor建立執行緒池:

    public ThreadPoolExecutor(int corePoolSize,                               int maximumPoolSize,                               long keepAliveTime,                               TimeUnit unit,                               BlockingQueue<Runnable> workQueue,                               ThreadFactory threadFactory,                               RejectedExecutionHandler handler)

  1. corePoolSize - 執行緒池核心池的大小。
  2. maximumPoolSize - 執行緒池的最大執行緒數。
  3. keepAliveTime - 當執行緒數大於核心時,此為終止前多餘的空閒執行緒等待新任務的最長時間。
  4. unit - keepAliveTime 的時間單位。
  5. workQueue - 用來儲存等待執行任務的佇列。
  6. threadFactory - 執行緒工廠。
  7. handler - 拒絕策略。

關注點1 執行緒池大小

執行緒池有兩個執行緒數的設定,一個為核心池執行緒數,一個為最大執行緒數。

在建立了執行緒池後,預設情況下,執行緒池中並沒有任何執行緒,等到有任務來才建立執行緒去執行任務,除非呼叫了prestartAllCoreThreads()或者prestartCoreThread()方法

當建立的執行緒數等於 corePoolSize 時,會加入設定的阻塞佇列。當佇列滿時,會建立執行緒執行任務直到執行緒池中的數量等於maximumPoolSize。

關注點2 適當的阻塞佇列

java.lang.IllegalStateException: Queue full

方法 丟擲異常 返回特殊值 一直阻塞 超時退出

插入方法 add(e) offer(e) put(e) offer(e,time,unit)

移除方法 remove() poll() take() poll(time,unit)

檢查方法 element() peek() 不可用 不可用

ArrayBlockingQueue :一個由陣列結構組成的有界阻塞佇列。

LinkedBlockingQueue :一個由連結串列結構組成的有界阻塞佇列。

PriorityBlockingQueue :一個支援優先順序排序的無界阻塞佇列。

DelayQueue: 一個使用優先順序佇列實現的無界阻塞佇列。

SynchronousQueue: 一個不儲存元素的阻塞佇列。

LinkedTransferQueue: 一個由連結串列結構組成的無界阻塞佇列。

LinkedBlockingDeque: 一個由連結串列結構組成的雙向阻塞佇列。

關注點3 明確拒絕策略

ThreadPoolExecutor.AbortPolicy: 丟棄任務並丟擲RejectedExecutionException異常。 (預設)

ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不丟擲異常。

ThreadPoolExecutor.DiscardOldestPolicy:丟棄佇列最前面的任務,然後重新嘗試執行任務(重複此過程)

ThreadPoolExecutor.CallerRunsPolicy:由呼叫執行緒處理該任務

說明:Executors 各個方法的弊端:

1)newFixedThreadPool 和 newSingleThreadExecutor:

主要問題是堆積的請求處理佇列可能會耗費非常大的記憶體,甚至 OOM。

2)newCachedThreadPool 和 newScheduledThreadPool:

主要問題是執行緒數最大數是 Integer.MAX_VALUE,可能會建立數量非常多的執行緒,甚至 OOM。

Executors

讓我們再看看Executors提供的那幾個工廠方法。

newSingleThreadExecutor

建立一個單執行緒的執行緒池。這個執行緒池只有一個執行緒在工作,也就是相當於單執行緒序列執行所有任務。如果這個唯一的執行緒因為異常結束,那麼會有一個新的執行緒來替代它。

此執行緒池保證所有任務的執行順序按照任務的提交順序執行。

new ThreadPoolExecutor(1, 1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>())

newFixedThreadPool

建立固定大小的執行緒池。每次提交一個任務就建立一個執行緒,直到執行緒達到執行緒池的最大大小。

執行緒池的大小一旦達到最大值就會保持不變,如果某個執行緒因為執行異常而結束,那麼執行緒池會補充一個新執行緒。

new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

newCachedThreadPool

建立一個可快取的執行緒池。如果執行緒池的大小超過了處理任務所需要的執行緒,

那麼就會回收部分空閒(60秒不執行任務)的執行緒,當任務數增加時,此執行緒池又可以智慧的新增新執行緒來處理任務。

此執行緒池不會對執行緒池大小做限制,執行緒池大小完全依賴於作業系統(或者說JVM)能夠建立的最大執行緒大小。

new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());

 

  1. 解釋下什麼是ThreadLocal,具體的使用場景

每一個ThreadLocal(執行緒副本)能夠放一個執行緒級別的變數,可是它本身能夠被多個執行緒共享使用,並且又能夠達到執行緒安全的目的,且絕對執行緒安全。

ThreadLocal 類的常用方法

ThreadLocal() : 建立一個執行緒本地變數  get() : 返回此執行緒區域性變數的當前執行緒副本中的值  initialValue() : 返回此執行緒區域性變數的當前執行緒的"初始值"  set(T value) : 將此執行緒區域性變數的當前執行緒副本中的值設定為value

ThreadLocal 底層原理是使用Map實現的,在使用時利用map的key儲存當前執行緒物件

,value儲存對應自定義的的執行緒本地變數值。

synchronized和Lock的區別:

synchronized同步鎖比較重效率低,比較耗資源,不靈活智;程式碼開始行自動上鎖解鎖;

lock鎖更靈活,使用起來更方便;手動上鎖解鎖;

 

  1. 怎麼建立一個執行緒

1)繼承Thread類建立執行緒

2)實現Runnable介面建立執行緒

3)使用Callable和Future建立執行緒

  1. 非同步執行緒

join 方法其實就是阻塞當前呼叫它的執行緒,等待join執行完畢,當前執行緒繼續執行

  1. 怎麼獲取一個執行緒執行後的結果

java中提供了Future<V>介面和實現了Future介面的FutureTask<V> 類來將執行緒執行之後的結果返回(通過get()方法)。

  1. 執行緒有幾種狀態

1. 新建(NEW):新建立了一個執行緒物件。

2. 可執行(RUNNABLE):執行緒物件建立後,其他執行緒(比如main執行緒)呼叫了該物件的start()方法。該狀態的執行緒位於可執行執行緒池中,等待被執行緒排程選中,獲取cpu 的使用權 。

3. 執行(RUNNING):可執行狀態(runnable)的執行緒獲得了cpu 時間片(timeslice) ,執行程式程式碼。

4. 阻塞(BLOCKED):阻塞狀態是指執行緒因為某種原因放棄了cpu 使用權,也即讓出了cpu timeslice,暫時停止執行。直到執行緒進入可執行(runnable)狀態,才有機會再次獲得cpu timeslice 轉到執行(running)狀態。阻塞的情況分三種: 

(一). 等待阻塞:執行(running)的執行緒執行o.wait()方法,JVM會把該執行緒放入等待佇列(waitting queue)中。

(二). 同步阻塞:執行(running)的執行緒在獲取物件的同步鎖時,若該同步鎖被別的執行緒佔用,則JVM會把該執行緒放入鎖池(lock pool)中。

(三). 其他阻塞:執行(running)的執行緒執行Thread.sleep(long ms)或t.join()方法,或者發出了I/O請求時,JVM會把該執行緒置為阻塞狀態。當sleep()狀態超時、join()等待執行緒終止或者超時、或者I/O處理完畢時,執行緒重新轉入可執行(runnable)狀態。

5. 死亡(DEAD):執行緒run()、main() 方法執行結束,或者因異常退出了run()方法,則該執行緒結束生命週期。死亡的執行緒不可再次復生。

  1. 什麼是volatile,具體使用場景

volatile特性:

1.保證變數線上程之間的可見性。

2.阻止編譯時和執行時的指令重排。

  1. Java樂觀鎖、悲觀鎖、Lock使用場景及方式

樂觀鎖:獲得鎖後一直持有鎖以防本執行緒再次申請該鎖造成無謂的解鎖再加鎖開銷,或者假設沒有衝突而去完成同步程式碼塊如果衝突再迴圈重試,或者採取申請鎖失敗後不立刻掛起而是稍微等待再次嘗試獲取 等待策略,以減少執行緒因為掛起、阻塞、喚醒(發生CPU的排程切換) 而造成的開銷。

悲觀鎖:不管是否發生多執行緒衝突,只要存在這種可能,就每次訪問都加鎖,加鎖就會導致鎖之間的爭奪,有爭奪就會有輸贏,輸者等待。

  1. synchronized
  1. synchronized關鍵字可以作為函式的修飾符,也可作為函式內的語句,也就是平時說的同步方法和同步語句塊。如果 再細的分類,synchronized可作用於instance變數、object reference(物件引用)、static函式和class literals(類名稱字面常量)身上。
  2. 無論synchronized關鍵字加在方法上還是物件上,它取得的鎖都是物件,而不是把一段程式碼或函式當作鎖――而且同步方法很可能還會被其他執行緒的物件訪問。
  3. 每個物件只有一個鎖(lock)與之相關聯。
  4. 實現同步是要很大的系統開銷作為代價的,甚至可能造成死鎖,所以儘量避免無謂的同步控制。

synchronized關鍵字的作用域有二種:

  1. 某個物件例項內,synchronized aMethod(){}可以防止多個執行緒同時訪問這個物件的synchronized方法(如果一個物件有多個synchronized方法,只要一個線 程訪問了其中的一個synchronized方法,其它執行緒不能同時訪問這個物件中任何一個synchronized方法)。這時,不同的物件例項的 synchronized方法是不相干擾的。也就是說,其它執行緒照樣可以同時訪問相同類的另一個物件例項中的synchronized方法;
  2. 某個類的靜態方法範圍,synchronized static aStaticMethod{}防止多個執行緒同時訪問這個類中的synchronized static 方法。它可以對類的所有物件例項起作用。

synchronized 修飾在 static方法和非static方法的區別:

synchronized是對類的當前例項(當前物件)進行加鎖,防止其他執行緒同時訪問該類的該例項的所有synchronized塊,注意這裡是“類的當前例項”, 類的兩個不同例項就沒有這種約束了。

static synchronized恰好就是要控制類的所有例項的併發訪問,static synchronized是限制多執行緒中該類的所有例項同時訪問jvm中該類所對應的程式碼塊。

常用框架相關:

JVM相關:

  1. Java記憶體模型

Java記憶體模型:

簡述下java記憶體模型:

java記憶體模型有 方法區、堆、虛擬機器棧、本地方法棧、程式計數器 組成;其中方法區也是各個執行緒共享的,主要用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、編譯後的程式碼等資料;虛擬機器棧是描述Java方法執行的記憶體模型;本地方法棧是執行的Native服務;程式計數器是當前執行緒所執行的的位元組碼的指示器,只佔用很小的記憶體空間。

虛擬機器棧

虛擬機器棧是執行緒私有的,而且它的生命週期和執行緒相同.虛擬機器棧是描述Java方法執行的記憶體模型。每個方法在執行時都會建立一個棧幀,用於儲存區域性變數表、運算元棧、動態連結和方法出口資訊等,這裡主要了解區域性變數表部分。

區域性變數表存放了編譯時可知的各種基本資料型別和物件引用。需要注意的是long和double資料會佔用2個區域性變數空間,其它的都佔一個。區域性變數表的大小在編譯時已經確定,所以在方法執行時不會改變區域性變數表的大小。

虛擬機器棧區域會出現StackOverflowError異常和OutOfMemoryError異常。

程式計數器

程式計數器可以看作是當前執行緒所執行的的位元組碼的指示器,只佔用很小的記憶體空間。每個執行緒都需要有一個獨立的程式計數器,各個執行緒之間的計數器互不影響,所以它也是執行緒隔離的資料區。

程式計數器是JVM中唯一一個沒有規定OOM的區域。

本地方法棧

本地方法棧和虛擬機器棧非常相似,它們的區別是虛擬機器棧執行的是Java方法服務,而本地方法棧執行的是Native服務。

本地方法棧區域也會出現StackOverflowError異常和OutOfMemoryError異常。

Java

Java堆是被所有執行緒共享的,在虛擬機器啟動的時候建立,它的唯一目的就是存放物件例項。也就是說所有的物件例項和陣列都要在堆上分配。

Java堆可以處於物理上不連續的記憶體空間,只要邏輯上是連續的即可。如果Java堆無法再繼續擴充套件,而又有物件例項未完成分配,將會丟擲OutOfMemoryError異常。

方法區

方法區也是各個執行緒共享的,主要用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、編譯後的程式碼等資料。

執行時常量池是方法區的一部分,主要用於存放編譯生成的各種字面量和符號引用。

Java 堆模型

 

年輕代什麼時候轉換為老年代?

新生代幾乎是所有 Java 物件出生的地方,即 Java 物件申請的記憶體以及存放都是在這個地方。Java 中的大部分物件通常不需長久存活,具有朝生夕滅的性質。 當一個物件被判定為 “死亡” 的時候,GC 就有責任來回收掉這部分物件的記憶體空間。新生代是 GC 收集垃圾的頻繁區域。 當物件在 Eden 出生後,在經過一次 Minor GC 後,如果物件還存活,並且能夠被另外一塊 Survivor 區域所容納,則使用複製演算法將這些仍然還存活的物件複製到另外一塊 Survivor 區域中,然後清理所使用過的 Eden 以及 Survivor 區域,並且將這些物件的年齡設定為1,以後物件在 Survivor 區每熬過一次 Minor GC,就將物件的年齡 + 1,當物件的年齡達到某個值時 ( 預設是 15 歲,可以通過引數 -XX:MaxTenuringThreshold 來設定 ),這些物件就會成為老年代。 但這也不是一定的,對於一些較大的物件 ( 即需要分配一塊較大的連續記憶體空間 ) 則是直接進入到老年代。

新生代的垃圾回收器共有三個:Serial,Parallel Scavenge 和 Parallel New。這三個採用的都是標記 - 複製演算法。其中,Serial 是一個單執行緒的,Parallel New 可以看成 Serial 的多執行緒版本。Parallel Scavenge 和 Parallel New 類似,但更加註重吞吐率。此外,Parallel Scavenge 不能與 CMS 一起使用。

老年代的垃圾回收器也有三個:剛剛提到的 Serial Old 和 Parallel Old,以及 CMSSerial Old 和 Parallel Old 都是標記 - 壓縮演算法。同樣,前者是單執行緒的,而後者可以看成前者的多執行緒版本。

CMS 採用的是標記 - 清除演算法,並且是併發的。除了少數幾個操作需要 Stop-the-world 之外,它可以在應用程式執行過程中進行垃圾回收。在併發收集失敗的情況下,Java 虛擬機器會使用其他兩個壓縮型垃圾回收器進行一次垃圾回收。由於 G1 的出現,CMS 在 Java 9 中已被廢棄

G1(Garbage First)是一個橫跨新生代和老年代的垃圾回收器。實際上,它已經打亂了前面所說的堆結構,直接將堆分成極其多個區域。每個區域都可以充當 Eden 區、Survivor 區或者老年代中的一個。它採用的是標記 - 壓縮演算法,而且和 CMS 一樣都能夠在應用程式執行過程中併發地進行垃圾回收

Java物件的建立

在Java中建立物件主要是通過new關鍵字,當虛擬機器遇到new指令時,首先去檢查這個指令的引數是否能在常量池中定位到一個類的符號引用.並檢查這個類是否已經被載入 解析和初始化,如果沒有先執行類的載入過程。

經過上面的步驟,確定類已經被載入後,JVM就會為新生物件分配記憶體.物件所需的記憶體大小在類載入完成後就已經確定,所以只需要在Java堆中劃分出確定大小的空間。記憶體的劃分方式分為”指標碰撞”和”空閒列表”。

物件的訪問

Java通過棧上的本地變數表的reference資料來操作Java堆上的物件。reference資料可以通過控制程式碼或者指標的方式區訪問物件。

通過控制程式碼方式的話,Java堆中會劃分出一塊記憶體來存放控制程式碼池,reference中儲存的是控制程式碼的地址,如圖:

指標訪問,reference中儲存的直接是物件的地址,如圖:

使用指標訪問的速度更快。

  1. 弱引用、強引用、軟引用、虛引用

    強引用:如“Object obj = new Object()”,這類引用是Java程式中最普遍的。只要強引用還存在,垃圾收集器就永遠不會回收掉被引用的物件。

    軟引用:它用來描述一些可能還有用,但並非必須的物件。在系統記憶體不夠用時,這類引用關聯的物件將被垃圾收集器回收。JDK1.2之後提供了SoftReference類來實現軟引用。

    弱引用:它也是用來描述非需物件的,但它的強度比軟引用更弱些,被弱引用關聯的物件只能生存島下一次垃圾收集發生之前。當垃圾收集器工作時,無論當前記憶體是否足夠,都會回收掉只被弱引用關聯的物件。在JDK1.2之後,提供了WeakReference類來實現弱引用。

    虛引用:最弱的一種引用關係,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個物件例項。為一個物件設定虛引用關聯的唯一目的是希望能在這個物件被收集器回收時收到一個系統通知。JDK1.2之後提供了PhantomReference類來實現虛引用。

堆疊的區別?

棧記憶體儲存的是區域性變數,堆記憶體儲存的是實體;

棧記憶體的更新速度要快於堆記憶體,因為區域性變數的生命週期很短;

棧記憶體存放的變數生命週期一旦結束就會被釋放,而堆記憶體存放的實體會被垃圾回收機制不定時的回收。

怎麼判斷物件是否可以被回收?

引用計數器:為每個物件建立一個引用計數,有物件引用時計數器 +1,引用被釋放時計數 -1,當計數器為 0 時就可以被回收。它有一個缺點不能解決迴圈引用的問題;

可達性分析目前 Java 虛擬機器的主流垃圾回收器採取的是可達性分析演算法。這個演算法的實質在於將一系列 GC Roots 作為初始的存活物件合集(live set),然後從該合集出發,探索所有能夠被該集合引用到的物件,並將其加入到該集合中,這個過程我們也稱之為標記(mark)。最終,未被探索到的物件便是死亡的,是可以回收的

  1. Java虛擬機器回收機制

垃圾物件的判定

    Java堆中存放著幾乎所有的物件例項,垃圾收集器對堆中的物件進行回收前,要先確定這些物件是否還有用,判定物件是否為垃圾物件有如下演算法:

幾種主要方式:

  1. 標記-清除:無用物件全部幹掉
  2. 標記-整理:有用物件都向一邊移動,邊界以外的全部幹掉
  3. 複製演算法:左邊記憶體快滿時,將其中要保留的物件複製到右邊記憶體中,然後整體幹掉左邊記憶體。右邊同理,記憶體利用率僅有一半
  4. 分代演算法:根據物件存活週期的不同將記憶體劃分為幾塊,一般是新生代和老年代,新生代基本採用複製演算法,老年代採用標記整理演算法

    引用計數演算法

    給物件新增一個引用計數器,每當有一個地方引用它時,計數器值就加1,當引用失效時,計數器值就減1,任何時刻計數器都為0的物件就是不可能再被使用的。

    引用計數演算法的實現簡單,判定效率也很高,在大部分情況下它都是一個不錯的選擇,當Java語言並沒有選擇這種演算法來進行垃圾回收,主要原因是它很難解決物件之間的相互迴圈引用問題

    根搜尋演算法

    Java和C#中都是採用根搜尋演算法來判定物件是否存活的。這種演算法的基本思路是通過一系列名為“GC Roots”的物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈,當一個物件到GC Roots沒有任何引用鏈相連時,就證明此物件是不可用的。在Java語言裡,可作為GC Roots的兌現包括下面幾種:

    虛擬機器棧(棧幀中的本地變數表)中引用的物件。

    方法區中的類靜態屬性引用的物件。

    方法區中的常量引用的物件。

    本地方法棧中JNI(Native方法)的引用物件。

    實際上,在根搜尋演算法中,要真正宣告一個物件死亡,至少要經歷兩次標記過程:如果物件在進行根搜尋後發現沒有與GC Roots相連線的引用鏈,那它會被第一次標記並且進行一次篩選,篩選的條件是此物件是否有必要執行finalize()方法。當物件沒有覆蓋finalize()方法,或finalize()方法已經被虛擬機器呼叫過,虛擬機器將這兩種情況都視為沒有必要執行。如果該物件被判定為有必要執行finalize()方法,那麼這個物件將會被放置在一個名為F-Queue佇列中,並在稍後由一條由虛擬機器自動建立的、低優先順序的Finalizer執行緒去執行finalize()方法。finalize()方法是物件逃脫死亡命運的最後一次機會(因為一個物件的finalize()方法最多隻會被系統自動呼叫一次),稍後GC將對F-Queue中的物件進行第二次小規模的標記,如果要在finalize()方法中成功拯救自己,只要在finalize()方法中讓該物件重引用鏈上的任何一個物件建立關聯即可。而如果物件這時還沒有關聯到任何鏈上的引用,那它就會被回收掉。

垃圾收集演算法

    判定除了垃圾物件之後,便可以進行垃圾回收了。下面介紹一些垃圾收集演算法,由於垃圾收集演算法的實現涉及大量的程式細節,因此這裡主要是闡明各演算法的實現思想,而不去細論演算法的具體實現。

    標記—清除演算法

    標記—清除演算法是最基礎的收集演算法,它分為“標記”和“清除”兩個階段:首先標記出所需回收的物件,在標記完成後統一回收掉所有被標記的物件,它的標記過程其實就是前面的根搜尋演算法中判定垃圾物件的標記過程。標記—清除演算法的執行情況如下圖所示:

   

   回收前狀態:

    回收後狀態:

    該演算法有如下缺點:

    標記和清除過程的效率都不高。

    標記清除後會產生大量不連續的記憶體碎片,空間碎片太多可能會導致,當程式在以後的執行過程中需要分配較大物件時無法找到足夠的連續記憶體而不得不觸發另一次垃圾收集動作。

    複製演算法

    複製演算法是針對標記—清除演算法的缺點,在其基礎上進行改進而得到的,它講課用記憶體按容量分為大小相等的兩塊,每次只使用其中的一塊,當這一塊的記憶體用完了,就將還存活著的物件複製到另外一塊記憶體上面,然後再把已使用過的記憶體空間一次清理掉。複製演算法有如下優點:

    每次只對一塊記憶體進行回收,執行高效。

    只需移動棧頂指標,按順序分配記憶體即可,實現簡單。

    記憶體回收時不用考慮記憶體碎片的出現。

    它的缺點是:可一次性分配的最大記憶體縮小了一半

    複製演算法的執行情況如下圖所示:

    回收前狀態:

   

 回收後狀態:

    標記—整理演算法

    複製演算法比較適合於新生代,在老年代中,物件存活率比較高,如果執行較多的複製操作,效率將會變低,所以老年代一般會選用其他演算法,如標記—整理演算法。該演算法標記的過程與標記—清除演算法中的標記過程一樣,但對標記後出的垃圾物件的處理情況有所不同,它不是直接對可回收物件進行清理,而是讓所有的物件都向一端移動,然後直接清理掉端邊界以外的記憶體。標記—整理演算法的回收情況如下所示:

   

  回收前狀態:

    回收後狀態:

分代收集

Java堆分為新生代和老年代。在新生代中,每次垃圾收集時都會發現有大量物件死去,只有少量存活,因此可選用複製演算法來完成收集,而老年代中因為物件存活率高、沒有額外空間對它進行分配擔保,就必須使用標記—清除演算法或標記—整理演算法來進行回收。

Java堆分為新生代和老年代。在新生代中,每次垃圾收集時都會發現有大量物件死去,只有少量存活,因此可選用複製演算法來完成收集,而老年代中因為物件存活率高、沒有額外空間對它進行分配擔保,就必須使用標記—清除演算法或標記—整理演算法來進行回收。

Redis:

快取雪崩:快取雪崩是指快取中資料大批量到過期時間,而查詢資料量巨大,引起資料庫壓力過大甚至down機。和快取擊穿不同的是,        快取擊穿指併發查同一條資料,快取雪崩是不同資料都過期了,很多資料都查不到從而查資料庫。

     解決方案:

快取資料的過期時間設定隨機,防止同一時間大量資料過期現象發生。

如果快取資料庫是分散式部署,將熱點資料均勻分佈在不同搞得快取資料庫中。

設定熱點資料永遠不過期。

 

快取穿透:是指惡意攻擊請求快取和資料庫中都沒有的資料,而使用者不斷髮起請求導致大量請求直接導向DB

解決方案:1.設定攻擊的key值為null  並設定短暫過期時間

2. 介面設定攔截,二次快取  第一次先把所有業務key統一列表快取,第一次快取查詢 key列表是否存在  不存在則拒絕處理

快取擊穿:快取擊穿是指快取中沒有但資料庫中有的資料(一般是快取時間到期),這時由於併發使用者特別多,同時讀快取沒讀到資料,又同時去資料庫去取資料,引起資料庫壓力瞬間增大,造成過大壓力

      解決方案:

  1. 設定熱點資料永遠不過期。
  2. 加互斥鎖,互斥鎖參考程式碼如下:

Redis 加鎖命令: setnx

設定過期日期 expire key second:設定key的過期時間(秒)

  1. Redis的五種儲存型別

String----------字串

Hash------------字典

List-------------列表

Set--------------集合

Sorted Set------有序集合

redis 記憶體資料集大小上升到一定大小的時候,就會施行資料淘汰策略(回收策略)。

redis 提供 6 種資料淘汰策略

  1. volatile-lru:從已設定過期時間的資料集(server.db[i].expires)中挑選最近最少使用的資料淘汰
  2. volatile-ttl:從已設定過期時間的資料集(server.db[i].expires)中挑選將要過期的資料淘汰
  3. volatile-random:從已設定過期時間的資料集(server.db[i].expires)中任意選擇資料淘汰
  4. allkeys-lru:從資料集(server.db[i].dict)中挑選最近最少使用的資料淘汰
  5. allkeys-random:從資料集(server.db[i].dict)中任意選擇資料淘汰
  6. no-enviction(驅逐):禁止驅逐資料

redis單執行緒的的優點:

1.redis是基於記憶體的,記憶體的讀寫速度非常快;

2.redis是單執行緒的,省去了很多上下文切換執行緒的時間(cpu在多執行緒之間進行輪流執行(搶戰cpu資源),而redis單執行緒的,因此避免了繁瑣的多執行緒上下文切換。);

3.redis使用多路複用技術,可以處理併發的連線(多個socket連線,複用-指的是複用一個執行緒);

redis預設過期策略:

Redis會定期主動淘汰一批已過去的key

惰性刪除為被動刪除:用到的時候才會去檢驗key是不是已過期,過期就刪除

惰性刪除為redis伺服器內建策略

定期刪除可以通過:

  1. 第一、配置redis.conf 的hz選項,預設為10 (即1秒執行10次,100ms一次,值越大說明重新整理頻率越快,最Redis效能損耗也越大) 
  2. 第二、配置redis.conf的maxmemory最大值,當已用記憶體超過maxmemory限定時,就會觸發主動清理策略

Redis的Hset和set的區別

hset/hget 儲存的是一個資料物件,相當於在學校塞入學生的時候,確定好了班級,查詢的時候,先找到班級再找學生。

對於大量資料而言 hset/hget 要優於 set/get。

  1. 簡單地比較Redis與Memcached的區別:

1 、Redis不僅僅支援簡單的k/v型別的資料,同時還提供list,set,zset,hash等資料結構的儲存。

2 、Redis支援資料的備份,即master-slave模式的資料備份。

3 、Redis支援資料的持久化,可以將記憶體中的資料保持在磁碟中,重啟的時候可以再次載入進行使用

  1. Redis、Memcache和MongoDB的區別

從以下幾個維度,對redis、memcache、mongoDB 做了對比,

1、效能

都比較高,效能對我們來說應該都不是瓶頸

總體來講,TPS方面redis和memcache差不多,要大於mongodb

2、操作的便利性

memcache資料結構單一

redis豐富一些,資料操作方面,redis更好一些,較少的網路IO次數

mongodb支援豐富的資料表達,索引,最類似關係型資料庫,支援的查詢語言非常豐富

3、記憶體空間的大小和資料量的大小

redis在2.0版本後增加了自己的VM特性,突破實體記憶體的限制;可以對key value設定過期時間(類似memcache)

memcache可以修改最大可用記憶體,採用LRU演算法

mongoDB適合大資料量的儲存,依賴作業系統VM做記憶體管理,吃記憶體也比較厲害,服務不要和別的服務在一起

4、可用性(單點問題)

對於單點問題,

redis,依賴客戶端來實現分散式讀寫;主從複製時,每次從節點重新連線主節點都要依賴整個快照,無增量複製,因效能和效率問題,

所以單點問題比較複雜;不支援自動sharding,需要依賴程式設定一致hash 機制。

一種替代方案是,不用redis本身的複製機制,採用自己做主動複製(多份儲存),或者改成增量複製的方式(需要自己實現),一致性問題和效能的權衡

Memcache本身沒有資料冗餘機制,也沒必要;對於故障預防,採用依賴成熟的hash或者環狀的演算法,解決單點故障引起的抖動問題。

mongoDB支援master-slave,replicaset(內部採用paxos選舉演算法,自動故障恢復),auto sharding機制,對客戶端遮蔽了故障轉移和切分機制。

5、可靠性(持久化)

對於資料持久化和資料恢復,

redis支援(快照、AOF):依賴快照進行持久化,aof增強了可靠性的同時,對效能有所影響

memcache不支援,通常用在做快取,提升效能;

MongoDB從1.8版本開始採用binlog方式支援持久化的可靠性

6、資料一致性(事務支援)

Memcache 在併發場景下,用cas保證一致性

redis事務支援比較弱,只能保證事務中的每個操作連續執行

mongoDB不支援事務

7、資料分析

mongoDB內建了資料分析的功能(mapreduce),其他不支援

8、應用場景

redis:資料量較小的更效能操作和運算上

memcache:用於在動態系統中減少資料庫負載,提升效能;做快取,提高效能(適合讀多寫少,對於資料量比較大,可以採用sharding)

MongoDB:主要解決海量資料的訪問效率問題

  1. 描述下臨界區

臨界區:臨界區用來表示一種公共資源或者是共享資料,可以被多個執行緒使用。但是每一次,只能有一個執行緒使用它,一旦臨界區資源被佔用,其他執行緒要想使用這個資源,就必須等待。

  1. RESTful架構風格

  1. SOA和微服務架構的區別?

SpringCloud專案簡介

 

   springCloud是基於SpringBoot的一整套實現微服務的框架。他提供了微服務開發所需的配置管理、服務發現、斷路器、智慧路由、微代理、控制匯流排、全域性鎖、決策競選、分散式會話和叢集狀態管理等元件。最重要的是,

  跟spring boot框架一起使用的話,會讓你開發微服務架構的雲服務非常好的方便。

  

  SpringBoot旨在簡化建立產品級的 Spring 應用和服務,簡化了配置檔案,使用嵌入式web伺服器,含有諸多開箱即用微服務功能

  

  相關元件架構圖

  

  spring cloud子專案包括:

  Spring Cloud Config:配置管理開發工具包,可以讓你把配置放到遠端伺服器,目前支援本地儲存、Git以及Subversion。

  Spring Cloud Bus:事件、訊息匯流排,用於在叢集(例如,配置變化事件)中傳播狀態變化,可與Spring Cloud Config聯合實現熱部署。

  Spring Cloud Netflix:針對多種Netflix元件提供的開發工具包,其中包括Eureka、Hystrix、Zuul、Archaius等。

  Netflix Eureka(讀音:優瑞卡):雲端負載均衡,一個基於 REST 的服務,用於定位服務,以實現雲端的負載均衡和中間層伺服器的故障轉移。我們可以將自己定義的API 介面註冊到Spring Cloud Eureka上,Eureka負責服務的註冊於發現,Eureka的角色和 Zookeeper的角色差不多,都是服務的註冊和發現,構成Eureka體系的包括:服務註冊中心、服務提供者、服務消費者。

  Netflix Hystrix(海斯拽克斯):容錯管理工具,旨在通過控制服務和第三方庫的節點,從而對延遲和故障提供更強大的容錯能力。Spring Cloud Hystrix是防止對某一故障服務持續進行訪問。Hystrix的含義是:斷路器,斷路器本身是一種開關裝置,用於我們家庭的電路保護,防止電流的過載,當線路中有電器發生短路的時候,斷路器能夠及時切換故障的電器,防止發生過載、發熱甚至起火等嚴重後果。

  Netflix Zuul(讀音:如兒):【服務閘道器】邊緣服務工具,是提供動態路由,監控,彈性,安全等的邊緣服務。服務閘道器是微服務架構中一個不可或缺的部分。通過服務閘道器統一向外系統提供REST API的過程中,除了具備服務路由、均衡負載功能之外,它還具備了許可權控制等功能。Spring Cloud Netflix中的Zuul就擔任了這樣的一個角色,為微服務架構提供了前門保護的作用,同時將許可權控制這些較重的非業務邏輯內容遷移到服務路由層面,使得服務叢集主體能夠具備更高的可複用性和可測試性。

  Netflix Archaius:配置管理API,包含一系列配置管理API,提供動態型別化屬性、執行緒安全配置操作、輪詢框架、回撥機制等功能。

  Spring Cloud for Cloud Foundry:通過Oauth2協議繫結服務到CloudFoundry,CloudFoundry是VMware推出的開源PaaS雲平臺。

  Spring Cloud Sleuth:日誌收集工具包,封裝了Dapper,Zipkin和HTrace操作。

  Spring Cloud Data Flow大資料操作工具,通過命令列方式運算元據流。

  Spring Cloud Security安全工具包,為你的應用程式新增安全控制,主要是指OAuth2。

  Spring Cloud Consul(讀音:康搜):封裝了Consul操作,consul是一個服務發現與配置工具,與Docker容器可以無縫整合。

  Spring Cloud Zookeeper(讀音:如kei普兒):操作Zookeeper的工具包,用於使用zookeeper方式的服務註冊和發現。

  Spring Cloud Stream資料流操作開發包,封裝了與Redis,Rabbit、Kafka等傳送接收訊息。

  Spring Cloud CLI基於 Spring Boot CLI,可以讓你以命令列方式快速建立雲元件。

  Spring Cloud Feign(讀音:芬兒):Spring Cloud Feign 是一個宣告web服務客戶端,這使得編寫Web服務客戶端更容易,使用Feign 建立一個介面並對它進行註解,它具有可插拔的註解支援包括Feign註解與JAX-RS註解,Feign還支援可插拔的編碼器與解碼器,Spring Cloud 增加了對 Spring MVC的註解,Spring Web 預設使用了HttpMessageConverters, Spring Cloud 整合 Ribbon 和 Eureka 提供的負載均衡的HTTP客戶端 Feign。簡單的可以理解為:Spring Cloud Feign 的出現使得Eureka和Ribbon的使用更為簡單。

--------------------- --------------------- --------------------- --------------------- --------------------- --------------

  SpringCloud特點

       1:約定優於配置

2:開箱即用、快速啟動

3:適用於各種環境

4:輕量級的元件

5:元件支援豐富,功能齊全

SpringBoot 常用註解

 

@EnableAutoConfiguration

啟動自動裝載:使用了這個註解之後,所有引入的jar的starters都會被自動注入。這個類的設計就是為starter工作的。

@RestController

這個註解專門用於寫RESTful的介面的,裡面整合了@Controller和@ResponseBody註解。 

@ResponseBody 這個註解會自動利用預設的Jackson將return的物件序列化成json格式。

@SpringBootApplication

@SpringBootApplication註解等價於以預設屬性使用 @Configuration , @EnableAutoConfiguration

和 @ComponentScan 。

RabbitMQ幾種模式

1交換機和交換機型別

2釋出/訂閱模式:

生產者將訊息傳送到指定的交換機,交換機再將訊息傳送到各個訊息佇列

3RPC模式

RPC工作方式:

當客戶端啟動時,會建立一個匿名的回撥佇列

在RPC請求中,定義了兩個屬性:replyTo,表示回撥佇列的名稱; correlationId,表示請求任務的唯一編號,用來區分不同請求的返回結果。

將請求傳送到rpc_queue佇列中

RPC伺服器等待rpc_queue佇列的請求,如果有訊息,就處理,它將計算結果傳送到請求中的回撥佇列裡。

客戶端監聽回撥佇列中的訊息,如果有返回訊息,它根據回撥訊息中的correlationid進行匹配計算結果。

4Topic模式,即匹配模式

通過匹配交換器,我們可以配置更靈活的訊息系統,你可以在匹配交換器模式下傳送這樣的路由關鍵字:

“a.b.c”、“c.d”、“quick.orange.rabbit”

不過一定要記住,路由關鍵字【routingKey】不能超過255個位元組(bytes)

匹配交換器的匹配符

*(星號)表示一個單詞

#(井號)表示零個或者多個單詞

Kafka面試題:

為什麼要是用訊息引擎?

削峰填谷,緩衝上下游瞬時突發流量,使其更平滑。特別是對於那種傳送能力很強的上游系統,如果沒有訊息引擎的保護,“脆弱”的下游系統可能會直接被壓垮導致全鏈路服務“雪崩”

kafka是怎麼保證高可用的?

kafka是用Replication(備份機制)和多個Broker(多個客戶端分散在不同伺服器)實現高可用的;備份的思想很簡單,就是把相同的資料拷貝到多臺機器上,而這些相同的資料拷貝在 Kafka 中被稱為副本(Replica)。

kafka的副本型別?

Kafka 定義了兩類副本:領導者副本(Leader Replica)和追隨者副本(Follower Replica)。前者對外提供服務,這裡的對外指的是與客戶端程式進行互動;而後者只是被動地追隨領導者副本而已,不能與外界進行互動。

kafka伺服器端Broker 

即一個 Kafka 叢集由多個 Broker 組成,Broker 負責接收和處理客戶端傳送過來的請求,以及對訊息進行持久化。雖然多個 Broker 程式能夠執行在同一臺機器上,但更常見的做法是將不同的 Broker 分散執行在不同的機器上,這樣如果叢集中某一臺機器當機,即使在它上面執行的所有 Broker 程式都掛掉了,其他機器上的 Broker 也依然能夠對外提供服務。這其實就是 Kafka 提供高可用的手段之一。

kafka副本的工作機制:生產者總是向領導者副本寫訊息;而消費者總是從領導者副本讀訊息。至於追隨者副本,它只做一件事:向領導者副本傳送請求,請求領導者把最新生產的訊息發給它,這樣它能保持與領導者的同步。

kafka有幾種訊息模式?

點對點模型也叫訊息佇列模型。

釋出 / 訂閱模型:一個主題(Topic)的概念,你可以理解成邏輯語義相近的訊息容器。該模型也有傳送方和接收方,只不過提法不同。傳送方也稱為釋出者(Publisher),接收方稱為訂閱者(Subscriber)。和點對點模型不同的是,這個模型可能存在多個釋出者向相同的主題傳送訊息,而訂閱者也可能存在多個,它們都能接收到相同主題的訊息。生活中的報紙訂閱就是一種典型的釋出 / 訂閱模型。副本機制可以保證資料的持久化或訊息不丟失

kafka怎麼解決副本太大的問題?

分割槽(Partitioning)把資料分割成多份儲存在不同的 Broker 上;Kafka 中的分割槽機制指的是將每個主題劃分成多個分割槽(Partition),每個分割槽是一組有序的訊息日誌。生產者生產的每條訊息只會被髮送到一個分割槽中,也就是說如果向一個雙分割槽的主題傳送一條訊息,這條訊息要麼在分割槽 0 中,要麼在分割槽 1 中。如你所見,Kafka 的分割槽編號是從 0 開始的,如果 Topic 有 100 個分割槽,那麼它們的分割槽號就是從 0 到 99。

Kafka 的三層訊息架構:

  1. 第一層是主題層,每個主題可以配置 M 個分割槽,而每個分割槽又可以配置 N 個副本。
  2. 第二層是分割槽層,每個分割槽的 N 個副本中只能有一個充當領導者角色,對外提供服務;其他 N-1 個副本是追隨者副本,只是提供資料冗餘之用。
  3. 第三層是訊息層,分割槽中包含若干條訊息,每條訊息的位移從 0 開始,依次遞增。
  4. 最後,客戶端程式只能與分割槽的領導者副本進行互動。

Kafka Broker 是如何持久化資料的?

Kafka 使用訊息日誌(Log)來儲存資料,一個日誌就是磁碟上一個只能追加寫(Append-only)訊息的物理檔案。因為只能追加寫入,故避免了緩慢的隨機 I/O 操作,改為效能較好的順序 I/O 寫操作,這也是實現 Kafka 高吞吐量特性的一個重要手段。在 Kafka 底層,一個日誌又近一步細分成多個日誌段訊息被追加寫到當前最新的日誌段中,當寫滿了一個日誌段後Kafka 會自動切分出一個新的日誌段,並將老的日誌段封存起來Kafka 在後臺還有定時任務會定期地檢查老的日誌段是否能夠被刪除,從而實現回收磁碟空間的目的。

kafka為什麼要引入消費者組呢?(Consumer Group)

主要是為了提升消費者端的吞吐量。多個消費者例項同時消費,加速整個消費端的吞吐量(TPS)。消費者組裡面的所有消費者例項不僅“瓜分”訂閱主題的資料,而且更酷的是它們還能彼此協助。假設組內某個例項掛掉了Kafka 能夠自動檢測到,然後把這個 Failed 例項之前負責的分割槽轉移給其他活著的消費者。這個過程就是 Kafka 中大名鼎鼎的“重平衡”(Rebalance)。

kafka怎麼知道被哪個消費者消費?

消費者位移(Consumer Offset);表徵消費者消費進度,每個消費者都有自己的消費者位移。

kafka核心元件圖:

冷門面試題:

  1. Map  key是否可以重複

在Java中,有一種key值可以重複的map,就是IdentityHashMap。在IdentityHashMap中,判斷兩個鍵值k1和 k2相等的條件是 k1 == k2 。在正常的Map 實現(如 HashMap)中,當且僅當滿足下列條件時才認為兩個鍵 k1 和 k2 相等:(k1==null ? k2==null : e1.equals(e2))。

分散式事務實現:

mysql  XA 實現

兩階段提交,三階段提交實現

事務補償機制(TCC)實現

TCC 採用最終一致性的方式實現了一種柔性分散式事務,與 XA 規範實現的二階事務不同的是,TCC 的實現是基於服務層實現的一種二階事務提交。

TCC 分為三個階段,即 Try、Confirm、Cancel 三個階段。

  1. Try 階段:主要嘗試執行業務,執行各個服務中的 Try 方法,主要包括預留操作;
  2. Confirm 階段:確認 Try 中的各個方法執行成功,然後通過 TM 呼叫各個服務的 Confirm 方法,這個階段是提交階段;
  3. Cancel 階段:當在 Try 階段發現其中一個 Try 方法失敗,例如預留資源失敗、程式碼異常等,則會觸發 TM 呼叫各個服務的 Cancel 方法,對全域性事務進行回滾,取消執行業務。

如果在 Confirm 和 Cancel 階段出現異常情況,那 TCC 該如何處理呢?

此時 TCC 會不停地重試呼叫失敗的 Confirm 或 Cancel 方法,直到成功為止。

如何設計一個秒殺系統?

減庫存幾種方式

  1. 下單減庫存,即當買家下單後,在商品的總庫存中減去買家購買數量。下單減庫存是最簡單的減庫存方式,也是控制最精確的一種,下單時直接通過資料庫的事務機制控制商品庫存,這樣一定不會出現超賣的情況。但是你要知道,有些人下完單可能並不會付款。
  2. 付款減庫存,即買家下單後,並不立即減庫存,而是等到有使用者付款後才真正減庫存,否則庫存一直保留給其他買家。但因為付款時才減庫存,如果併發比較高,有可能出現買家下單後付不了款的情況,因為可能商品已經被其他人買走了。
  3. 預扣庫存,這種方式相對複雜一些,買家下單後,庫存為其保留一定的時間(如 10 分鐘),超過這個時間,庫存將會自動釋放,釋放後其他買家就可以繼續購買。在買家付款前,系統會校驗該訂單的庫存是否還有保留:如果沒有保留,則再次嘗試預扣;如果庫存不足(也就是預扣失敗)則不允許繼續付款;如果預扣成功,則完成付款並實際地減去庫存。

具體程式碼處理:

一種是在應用程式中通過事務來判斷,即保證減後庫存不能為負數,否則就回滾;

另一種辦法是直接設定資料庫的欄位資料為無符號整數,這樣減後庫存欄位值小於零時會直接執行 SQL 語句來報錯;

再有一種就是使用 CASE WHEN 判斷語句,例如這樣的 SQL 語句:

UPDATE item SET inventory = CASE WHEN inventory >= xxx THEN inventory-xxx ELSE inventory END

秒殺減庫存的極致優化

秒殺中並不需要對庫存有精確的一致性讀,把庫存資料放到快取(Cache)中,可以大大提升讀效能。

把秒殺商品減庫存直接放到快取系統(Redis)中實現;

  1. 應用層做排隊。按照商品維度設定佇列順序執行,這樣能減少同一臺機器對資料庫同一行記錄進行操作的併發度,同時也能控制單個商品佔用資料庫連線的數量,防止熱點商品佔用太多的資料庫連線。
  2. 資料庫層做排隊。應用層只能做到單機的排隊,但是應用機器數本身很多,這種排隊方式控制併發的能力仍然有限,所以如果能在資料庫層做全域性排隊是最理想的。阿里的資料庫團隊開發了針對這種 MySQL 的 InnoDB 層上的補丁程式(patch),可以在資料庫層上對單行記錄做到併發排隊。

Mysql 樂觀鎖和悲觀鎖的實現:

悲觀鎖”即認為資料出現衝突的可能性更大,

而“樂觀鎖”則是認為大部分情況不會出現衝突,進而決定是否採取排他性措施。

  悲觀鎖一般就是利用類似 SELECT … FOR UPDATE 這樣的語句,對資料加鎖,避免其他事務意外修改資料

   樂觀鎖則與 Java 併發包中的 AtomicFieldUpdater 類似,也是利用 CAS 機制,並不會對資料加鎖,而是通過對比資料的時間戳或者版本號,來實現樂觀鎖需要的版本判斷。

分散式鎖實現的幾種方式:

  1. 資料庫實現分散式鎖

建立一個鎖表,通過建立和查詢資料來保證一個資料的原子性

  1. Zookeeper 實現分散式鎖

  1. Redis 實現分散式鎖

Redis 實現分散式鎖的方式,都是使用 SETNX+EXPIRE 組合來實現;

通過 setnx() 方法設定鎖,如果 lockKey 存在,則返回失敗,否則返回成功。設定成功之後,為了能在完成同步程式碼之後成功釋放鎖,方法中還需要使用 expire() 方法給 lockKey 值設定一個過期時間,確認 key 值刪除,避免出現鎖無法釋放,導致下一個執行緒無法獲取到鎖,即死鎖問題。

public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {

 

    Long result = jedis.setnx(lockKey, requestId);// 設定鎖

    if (result == 1) {// 獲取鎖成功

        // 若在這裡程式突然崩潰,則無法設定過期時間,將發生死鎖

        jedis.expire(lockKey, expireTime);// 通過過期時間刪除鎖

        return true;

    }

    return false;

}

如果程式在設定過期時間之前、設定鎖之後出現崩潰,此時如果 lockKey 沒有設定過期時間,將會出現死鎖問題

  1. Redlock 演算法

Redisson 中實現了 Redis 分散式鎖,且支援單點模式和叢集模式。在叢集模式下,Redisson 使用了 Redlock 演算法避免在 Master 節點崩潰切換到另外一個 Master 時,多個應用同時獲得鎖。

在同樣的伺服器配置下,Redis 的效能是最好的,Zookeeper 次之,資料庫最差。

相關文章