一.JavaSE 部分
基礎篇
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碼,用單引號賦值,
Integer 和 int的區別
int是基本資料型別,變數中直接存放數值,變數初始化時值是0
Integer是參考資料型別,變數中存放的是該物件的參考,變數初始化時值時null
Integer是int型別的包裝類,將int封裝成Integer,符合java面向物件的特性,可以使用各種方法比如和其他資料型別間的轉換
Integer和int的深入對比:
-
兩個通過new生成的Integer物件,由于在堆中地址不同,所以永遠不相等
-
int和Integer比較時,只要數值相等,結果就相等,因為包裝類和基本資料型別比較時,會自動拆箱,將Integer轉化為int
-
通過new生成的Integer物件和非通過new生成的Integer物件相比較時,由于前者存放在堆中,后者存放在Java常量池中,所以永遠不相等
-
兩個非通過new生成的Integer物件比較時,如果兩個變數的數值相等且在-128到127之間,結果就相等,這是因為給Integer物件賦一個int值,java在編譯時,會自動呼叫靜態方法valueOf(),根據java api中對Integer型別的valueOf的定義,對于-128到127之間的整數,會進行快取,如果下次再賦相同的值會直接從快取中取,即享元模式
String和StringBuilder和StringBuffer區別
三者底層都是char[]存盤資料,JDK1.9之后使用的是byte[] ,因為往往我們存盤都是短字串,使用byte[]這樣更節約空間,
由于String底層的char[]有final修飾,因此每次對String的操作都會在記憶體中開辟空間,生成新的物件,所以String不可變
StringBuilder和StringBuffer是可變字串,沒有final修飾,適合字串拼接,另外StringBuffer是執行緒安全的,方法有synchronized修飾,但是性能較低,StringBuilder是執行緒不安全的,方法沒有synchronized修飾,性能較高
String a = “A” 和 String a = new String(“A”) 創建字串的區別
String c = “A” 首先去常量池找 “A”,如果有,會把a指向這個物件的地址 ,如果沒有則在堆疊中創建三個char型的值’A’,堆中創建一個String物件object,值為"A",接著object會被存放進字串常量池中,最后將a指向這個物件的的地址
new String(“A”) : 如果常量池中么有“A”就會走上面相同的流程先創建“A”,然后在堆中創建一個String物件,它的值共享堆疊中已有的char值“A”,
下面代碼創建了幾個物件
- String s = “a” +“b” + “c” + “d”;這條陳述句創建了幾個物件?
創建了一個物件,因為相對于字串常量相加的運算式,編譯器會在編譯期間進行優化,直接將其編譯成常量相加的結果,
-
String s; 創建幾個物件?
沒有創建物件, -
String a = “abc”; String b = “abc”; 創建了幾個物件
創建了一個物件,只是在第一條陳述句中創建了一個物件,a和b都指向相同的物件"abc",參考不是物件
== 和 equals 的區別是什么
==比較物件比較的是地址,對于Object物件中的equals 方法使用的也是 == ,比較的是物件的地址,默認情況下使用物件的equals比較Object中的equals方法,也就是比較地址,如果要實作自己的比較方式需要復寫equals 方法,
對于包裝類比如:Integer都是復寫過equals方法,比較的是int 值,
final 和 finally 和 finalize 的區別
當用final修飾類的時,表明該類不能被其他類所繼承,當我們需要讓一個類永遠不被繼承,此時就可以用final修飾
finally作為例外處理的一部分,它只能用在try/catch陳述句中,并且附帶一個陳述句塊,表示這段陳述句最終一定會被執行(不管有沒有拋出例外),經常被用在需要釋放資源的情況下
finalize()是在java.lang.Object里定義的,也就是說每一個物件都有這么個方法,這個方法在gc啟動,該物件被回收的時候被呼叫,其實gc可以回收大部分的物件(凡是new出來的物件,gc都能搞定,一般情況下我們又不會用new以外的方式去創建物件),所以一般是不需要程式員去實作finalize的,
JDK 和 JRE 有什么區別?
JRE(Java Runtime Enviroment) :是Java的運行環境,JRE是運行Java程式所必須環境的集合,包含JVM標準實作及 Java核心類別庫
JDK(Java Development Kit) :是Java開發工具包,它提供了Java的開發環境(提供了編譯器javac等工具,用于將java檔案編譯為class檔案)和運行環境(提 供了JVM和Runtime輔助包,用于決議class檔案使其得到運行),JDK是整個Java的核心,包括了Java運行環境(JRE),一堆Java工具tools.jar和Java標準類別庫 (rt.jar),
面向物件四大特性
抽象 : 是將一類物件的共同特征總結出來構造類的程序,包括資料抽象和行為抽象兩方面,抽象只關注物件的哪些屬性和行為,并不關注這此行為的細節是什么 - 舉例:定義一個persion類,了就是對人這種事物的抽象
封裝:對資料的訪問只能通過已定義的介面,封裝就是隱藏一切可隱藏的東西,只向外界提供最簡單的編程介面,比如在Java中,把不需要暴露的內容和實作細節隱藏起來,或者private修飾,然后提供專門的訪問方法,如JavaBean, - 生活舉例:電腦主機就是把主板等封裝到機殼,提供USB介面,網卡介面,電源介面等, JavaBean就是一種封裝,
繼承:新類(子類,派生類)繼承了原始類的特性,子類可以從它的父類哪里繼承方法和實體變數,并且類可以修改或增加新的方法使之更適合特殊的需要,
多型:多型是指允許不同類的物件對同一訊息做出回應,物件的多種形態,當編譯時型別和運行時型別不一樣,就是多型,意義在于屏蔽子類差異
方法覆寫和多載
方法的覆寫是子類和父類之間的關系,方法的多載是同一個類中方法之間的關系,
覆寫只能由一個方法,或只能由一對方法產生關系;方法的多載是多個方法之間的關系,
覆寫要求引數串列相同;多載要求引數串列不同,
普通類和抽象類
抽象類不能被實體化, 需要通過子類實體化
抽象類可以有建構式,被繼承時子類必須繼承父類一個構造方法,抽象方法不能被宣告為靜態,
抽象方法只需申明,而無需實作,抽象類中可以允許普通方法有主體
含有抽象方法的類必須申明為抽象類
抽象的子類必須實作抽象類中所有抽象方法,否則這個子類也是抽象類
介面和抽象類
定義介面使用interface,定義抽象類使用abstract class
介面由全域常量,抽象方法,(java8后:靜態方法,默認方法)
抽象類由構造方法,抽象方法,普通方法
介面和類是實作關系,抽象類和類是繼承關系
IO流
你知道BIO,NIO,AIO么?講一下你的理解
BIO (Blocking I/O):同步阻塞I/O 模式,以流的方式處理資料,資料的讀取寫入必須阻塞在一個執行緒內等待其完成,適用于連接數目比較小且固定的架構
NIO (New I/O):同時支持阻塞與非阻塞模式,以塊的方式處理資料,適用于連接數目多且連接比較短(輕操作)的架構,比如聊天器
AIO ( Asynchronous I/O):異步非阻塞I/O 模型,適用于連接數目多且連接比較長(重操作)的架構
java 中四大基礎流
InputStream : 輸入位元組流, 也就是說它既屬于輸入流, 也屬于位元組流 ,
OutputStream: 輸出位元組流, 既屬于輸出流, 也屬于位元組流
Reader: 輸入字符流, 既屬于輸入流, 又屬于字符流
Writer: 輸出字符流, 既屬于輸出流, 又屬于字符流
讀文本用什么流,讀圖片用什么流
文本用字符輸入流,讀圖片用直接輸入流
字符流和位元組流有什么區別
字符流適用于讀文本,位元組流適用于讀圖片,視頻,檔案等,
位元組流操作的基本單元為位元組;字符流操作的基本單元為Unicode碼元,
位元組流默認不使用緩沖區;字符流使用緩沖區,
位元組流通常用于處理二進制資料,實際上它可以處理任意型別的資料,但它不支持直接寫入或讀取Unicode碼元;字符流通常處理文本資料,它支持寫入及讀取Unicode碼元
BufferedInputStream 用到什么設計模式
主要運用了倆個設計模式,配接器和裝飾者模式
帶緩沖區的流
BufferedInputStream 帶緩沖區的位元組輸入
BufferedOutputStream 帶緩沖區的輸出流
BufferedReader : 帶緩沖區的字符輸入流
BufferedWriter : 帶緩沖區的字符輸出流
集合篇
說一下Java中的集合體系
Collection介面
List:
-
ArrayList:底層資料結構是陣列,查詢性能高,增刪性能低
-
Vector:底層資料結構是陣列,查詢性能高,增刪性能低
-
LinkedList:底層資料結構是雙向鏈表,查詢性能低,增刪性能高
Set:
-
HashSet:無序不重復的,使用HashMap的key存盤元素,判斷重復依據是hashCode()和equals()
-
TreeSet:有序不重復的,底層使用TreeMap的key存盤元素,排序方式分為自然排序,比較器排序
Map介面
- HashMap:key的值沒有順序,執行緒不安
- TreeMap:key的值可以自然排序,執行緒不安全
- HashTable:它的key和value都不允許為null,執行緒安全
- Properties:它的key和value都是String型別的,執行緒安全
HashMap和HashTable的區別
HashMap和HashTable都是實作了Map介面的集合框架,他們的區別
-
HashTable是執行緒安全的,它的實作方法都加了synchronized關鍵字,因此它的性能較低
-
HashMap是執行緒不安全的,它實作方法沒有加synchronized,因此它的性能較高
-
HashMap的key和value都允許為null,HashTable中的key和value都不能為null,如果不考慮執行緒安全,建議使用HashMap,如果需要考慮執行緒安全的高并發實作,建議使用ConcurrentHashMap
ArrayList和LinkedList區別
都屬于線性結構,ArrayList是基于陣列實作的,開辟的記憶體空間要求聯系,可以根據索引隨機訪問元素性能高,但是插入和洗掉元素性能差,因為這會涉及到移位操作
LinkedList是基于雙鏈表實作的,開配的記憶體空間不要求連續,因此不支持索引,查找元素需要從頭查找,因此性能差,但是添加洗掉只需要改變指標指向即可,性能高. LinkedList會增加記憶體碎片化,增加記憶體管理難度
根據實際需要,如果專案中使用查找較多,使用ArrayList,如果使用增刪較多,請使用LinkedList
ArrayList和Vector區別
ArrayList是執行緒不安全的,Vector相反是執行緒安全的,方法加了同步鎖,執行緒安全但是性能差,ArrayList底層陣列容量不足時,會自動擴容0.5倍,Vector會自動擴容1倍
一個User的List集合,如何實作根據年齡排序
第一種方式,讓User類實作Comparable介面,覆寫compareTo方法,方法中自定義根據年齡比較的演算法
第二種方式,呼叫Collections.sort方法,傳入一個比較器,覆寫compare方法,方法中自定義根據年齡比較的演算法
HashMap底層用到了那些資料結構?
JDK1.7及其之前:陣列,鏈表 ; JDK1.8開始:陣列,鏈表,紅黑樹
什么是Hash沖突
哈希沖突,也叫哈希碰撞,指的是兩個不同的值,計算出了相同的hash,也就是兩個不同的資料計算出同一個下標,通常解決方案有:
-
拉鏈法,把哈希碰撞的元素指向一個鏈表
-
開放尋址法,把產生沖突的哈希值作為值,再進行哈希運算,直到不沖突
-
再散列法,就是換一種哈希演算法重來一次
-
建立公共溢位區,把哈希表分為基本表和溢位表,將產生哈希沖突的元素移到溢位表
HashMap為什么要用到鏈表結構
當我們向HashMap中添加元素時,會先根據key盡心哈希運算,把hash值模與陣列長度得到一個下標,然后將該元素添加進去,但是如果產生了哈希碰撞,也就是不同的key計算出了相同的hash值,這就出問題了,因此它采用了拉鏈法來解決這個問題,將產生hash碰撞的元素,掛載到鏈表中
HashMap為什么要用到紅黑樹
當HashMap中同一個索引位置出現哈希碰撞的元素多了,鏈表會變得越來越長,查詢效率會變得越來越慢,因此在JDK1.8之后,當鏈表長度超過8個,會將鏈表轉壞為紅黑樹來提高查詢
HashMap鏈表和紅黑樹在什么情況下轉換的?
當鏈表的長度大于等于8,同時陣列的長度大于64,鏈表會自動轉化為紅黑樹,當樹中的節點數小于等于6,紅黑樹會自動轉化為鏈表
HashMap在什么情況下擴容?HashMap如何擴容的?
HashMap的陣列初始容量是16,負載因子是0.75,也就是說當陣列中的元素個數大于12個,會成倍擴容
tips:為啥子是0.75:負載因子過小容易浪費空間,過大容易造成更多的哈希碰撞,產生更多的鏈表和樹,因此折衷考慮采用了0.75
為啥子是成倍擴容:需要保證陣列的長度是2的整數次冪
為嘛陣列的長度必須是2的整數次冪:我們在存盤元素到陣列中的時候,是通過hash值模與陣列的長度,計算出下標的,但是由于計算機的運算效率,加減法>乘法>除法>取模,取模的效率是最低的,開發者們為了讓你用的開心,也是嘔心瀝血,將取模運算轉化成了與運算,即陣列長度減1的值和hash值的與運算,以此來優化性能,但是這個轉化有一個前提,就是陣列的長度必須為2的整數次冪
HashMap是如何Put一個元素的
首先,將key進行hash運算,將這個hash值與上當前陣列長度減1的值,計算出索引,此時判斷該索引位置是否已經有元素了,如果沒有,就直接放到這個位置
如果這個位置已經有元素了,也就是產生了哈希碰撞,那么判斷舊元素的key和新元素的key的hash值是否相同,并且將他們進行equals比較,如果相同證明是同一個key,就覆寫舊資料,并將舊資料回傳,如果不相同的話
再判斷當前桶是鏈表還是紅黑樹,如果是紅黑樹,就按紅黑樹的方式,寫入該資料,
如果是鏈表,就依次遍歷并比較當前節點的key和新元素的key是否相同,如果相同就覆寫,如果不同就接著往下找,直到找到空節點并把資料封裝成新節點掛到鏈表尾部,然后需要判斷,當前鏈表的長度是否大于轉化紅黑樹的閾值,如果大于就轉化紅黑樹,最后判斷陣列長度是否需要擴容,
HashMap是如何Get一個元素的
首先將key進行哈希運算,計算出陣列中的索引位置,判斷該索引位置是否有元素,如果沒有,就回傳null,如果有值,判斷該資料的key是否為查詢的key,如果是就回傳當前值的value
如果第一個元素的key不匹配,判斷是紅黑樹還是鏈表,如果是紅黑樹,就就按照紅黑樹的查詢方式查找元素并回傳,如果是鏈表,就遍歷并匹配key,讓后回傳value值
你知道HahsMap死回圈問題嗎
HashMap在擴容陣列的時候,會將舊資料遷徙到新陣列中,這個操作會將原來鏈表中的資料顛倒,比如a->b->null,轉換成b->a->null
這個程序單執行緒是沒有問題的,但是在多執行緒環境,就可能會出現a->b->a->b…,這就是死回圈
在JDK1.8后,做了改進保證了轉換后鏈表順序一致,死回圈問題得到了解決,但還是會出現高并發時資料丟失的問題,因此在多執行緒情況下還是建議使用ConcurrentHashMap來保證執行緒安全問題
說一下你對ConcurrentHashMap的理解
ConcurrentHashMap,它是HashMap的執行緒安全,支持高并發的版本
在jdk1.7中,它是通過分段鎖的方式來實作執行緒安全的,意思是將哈希表分成許多片段Segment,而Segment本質是一個可重入的互斥鎖,所以叫做分段鎖,
在jdk1.8中,它是采用了CAS操作和synchronized來實作的,而且每個Node節點的value和next都用了volatile關鍵字修飾,保證了可見性
多執行緒
創建執行緒是幾種方式
方式一:繼承Thread類,覆寫run方法,創建實體物件,呼叫該物件的start方法啟動執行緒
方式二:創建Runnable介面的實作類,類中覆寫run方法,再將實體作為此引數傳遞給Thread類有參構造創建執行緒物件,呼叫start方法啟動
方式三:創建Callable介面的實作類,類中覆寫call方法,創建實體物件,將其作為引數傳遞給FutureTask類有參構造創建FutureTask物件,再將FutureTask物件傳遞給Thread類的有參構造創建執行緒物件,呼叫start方法啟動
Thread有單繼承的局限性,Runnable和Callable三避免了單繼承的局限,使用更廣泛,Runnable適用于無需回傳值的場景,Callable使用于有回傳值的場景
Thread的start和run的區別
start是開啟新執行緒, 而呼叫run方法是一個普通方法呼叫,還是在主執行緒里執行,沒人會直接呼叫run方法
sleep 和 wait的區別
第一,sleep方法是Thread類的靜態方法,wait方法是Object類的方法
第二:sleep方法不會釋放物件鎖,wait方法會釋放物件鎖
第三:sleep方法必須捕獲例外,wait方法不需要捕獲例外
執行緒的幾種狀態
新建狀態:執行緒剛創建,還沒有呼叫start方法之前
就緒狀態:也叫臨時阻塞狀態,當呼叫了start方法后,具備cpu的執行資格,等待cpu調度器輪詢的狀態
運行狀態:就緒狀態的執行緒,獲得了cpu的時間片,真正運行的狀態
凍結狀態:也叫阻塞狀態,指的是該執行緒因某種原因放棄了cpu的執行資格,暫時停止運行的狀態,比如呼叫了wait,sleep方法
死亡狀態:執行緒執行結束了,比如呼叫了stop方法
Synchronized 和 lock的區別
他們都是用來解決并發編程中的執行緒安全問題的,不同的是
- synchronized是一個關鍵字,依靠Jvm內置語言實作,底層是依靠指令碼來實作;Lock是一個介面,它基于CAS樂觀鎖來實作的
- synchronized在執行緒發生例外時,會自動釋放鎖,不會發生例外死鎖,Lock在例外時不會自動釋放鎖,我們需要在finally中釋放鎖
- synchronized是可重入,不可判斷,非公平鎖,Lock是可重入,可判斷的,可手動指定公平鎖或者非公平鎖
你知道AQS嗎
AQS:AbstractQuenedSynchronizer抽象的佇列式同步器,是除了java自帶的synchronized關鍵字之外的鎖機制,它維護了一個volatile修飾的 int 型別的,state(代表共享資源)和一個FIFO執行緒等待佇列(多執行緒爭用資源被阻塞時會進入此佇列),
作業思想是如果被請求的資源空閑,也就是還沒有執行緒獲取鎖,將當前請求資源的執行緒設定為有效的作業執行緒,并將共享資源設定為鎖定狀態,如果請求的資源被占用,就將獲取不到鎖的執行緒加入佇列,
悲觀鎖和樂觀鎖
悲觀鎖和樂觀鎖,指的是看待并發同步問題的角度
-
悲觀鎖認為,對同一個資料的并發操作,一定是會被其他執行緒同時修改的,所以在每次操作資料的時候,都會上鎖,這樣別人就拿不到這個資料,如果不加鎖,并發操作一定會出問題,用陽間的話說,就是總有刁民想害朕
-
樂觀鎖認為,對同一個資料的并發操作,是不會有其他執行緒同時修改的,它不會使用加鎖的形式來操作資料,而是在提交更新資料的時候,判斷一下在操作期間有沒有其他執行緒修改了這個資料
悲觀鎖一般用于并發小,對資料安全要求高的場景,樂觀鎖一般用于高并發,多讀少寫的場景,通常使用版本號控制,或者時間戳來解決.
你知道什么是CAS嘛
CAS,compare and swap的縮寫,中文翻譯成比較并交換,它是樂觀鎖的一種體現,CAS 操作包含三個運算元 —— 記憶體位置(V)、預期原值(A)和新值(B), 如果記憶體位置的值與預期原值相匹配,那么處理器會自動將該位置值更新為新值 ,否則,處理器不做任何操作,
Synchronized 加非靜態和靜態方法上的區別
實體方法上的鎖,鎖住的是這個物件實體,它不會被實體共享,也叫做物件鎖
靜態方法上的鎖,鎖住的是這個類的位元組碼物件,它會被所有實體共享,也叫做類鎖
Synchronized(this) 和 Synchronized (User.class)的區別
Synchronized(this) 中,this代表的是該物件實體,不會被所有實體共享
Synchronized (User.class),代表的是對類加鎖,會被所有實體共享
Synchronized 和 volatitle 關鍵字的區別
這兩個關鍵字都是用來解決并發編程中的執行緒安全問題的,不同點主要有以下幾點
第一:volatile的實作原理,是在每次使用變數時都必須重主存中加載,修改變數后都必須立馬同步到主存;synchronized的實作原理,則是鎖定當前變數,讓其他執行緒處于阻塞狀態
第二:volatile只能修飾變數,synchronized用在修飾方法和同步代碼塊中
第三:volatile修飾的變數,不會被編譯器進行指令重排序,synchronized不會限制指令重排序
第四:volatile不會造成執行緒阻塞,高并發時性能更高,synchronized會造成執行緒阻塞,高并發效率低
第五:volatile不能保證操作的原子性,因此它不能保證執行緒的安全,synchronized能保證操作的原子性,保證執行緒的安全
synchronized 鎖的原理
synchronized是基于JVM內置鎖實作,通過內部物件Monitor(監視器鎖)實 現,基于進入與退出Monitor物件實作方法與代碼塊同步,監視器鎖的實作依賴 底層作業系統的Mutex lock(互斥鎖)實作,它是一個重量級鎖性能較低,涉及到用戶態到內核態的切換,會讓整個程式性能變得很差,
因此在JDK1.6及以后的版本中,增加了鎖升級的程序,依次為無鎖,偏向鎖,輕量級鎖,重量級鎖,而且還增加了鎖粗化,鎖消除等策略,這就節省了鎖操作的開銷,提高了性能
synchronized 鎖升級原理
每個物件都擁有物件頭,物件頭由Mark World ,指向類的指標,以及陣列長度三部分組成,鎖升級主要依賴Mark Word中的鎖標志位和釋放偏向鎖標識位,
- 偏向鎖(無鎖)
大多數情況下鎖不僅不存在多執行緒競爭,而且總是由同一執行緒多次獲得,偏向鎖的目的是在某個執行緒 獲得鎖之后(執行緒的id會記錄在物件的Mark Word鎖標志位中),消除這個執行緒鎖重入(CAS)的開銷,看起來讓這個執行緒得到了偏護,(第二次還是這個執行緒進來就不需要重復加鎖,基本無開銷),如果自始至終使用鎖的執行緒只有一個,很明顯偏向鎖幾乎沒有額外開銷,性能極高,
- 輕量級鎖(CAS):
輕量級鎖是由偏向鎖升級來的,偏向鎖運行在一個執行緒進入同步塊的情況下,當第二個執行緒加入鎖爭用的時候,偏向鎖就會升級為輕量級鎖自旋鎖);沒有搶到鎖的執行緒將自旋,獲取鎖的操作,輕量級鎖的意圖是在沒有多執行緒競爭的情況下,通過CAS操作嘗試將MarkWord鎖標志位更新為指向LockRecord的指標,減少了使用重量級鎖的系統互斥量產生的性能消耗,
長時間的自旋操作是非常消耗資源的,一個執行緒持有鎖,其他執行緒就只能在原地空耗CPU,執行不了任何有效的任務,這種現象叫做忙等(busy-waiting)
- 重量級鎖:
如果鎖競爭情況嚴重,某個達到最大自旋次數(10次默認)的執行緒,會將輕量級鎖升級為重量級鎖,重量級鎖則直接將自己掛起,在JDK1.6之前,synchronized直接加重量級鎖,很明顯現在得到了很好的優化,
虛擬機使用CAS操作嘗試將MarkWord更新為指向LockRecord的指標,如果更新成功表示執行緒就擁有該物件的鎖;如果失敗,會檢查MarkWord是否指向當前執行緒的堆疊幀,如果是,表示當前執行緒已經擁有這個鎖;如果不是,說明這個鎖被其他執行緒搶占,此時膨脹為重量級鎖,
樂觀鎖的使用場景(資料庫,ES)
場景一:ES中對version的控制并發寫,
場景二:資料庫中使用version版本號控制來防止更新覆寫問題,
場景三:原子類中的CompareAndSwap操作
AtomicInterger怎么保證并發安全性的
通過CAS操作原理來實作的,就可見性和原子性兩個方面來說
它的value值使用了volatile關鍵字修飾,也就保證了多執行緒操作時記憶體的可見性
Unsafe這個類是一個很神奇的類,而compareAndSwapInt這個方法可以直接操作記憶體,依靠的是C++來實作的,它呼叫的是Atomic類的cmpxchg函式,而這個函式的實作是跟作業系統有關的,比如在X86的實作就利用匯編語言的CPU指令lock cmpxchg,它在執行后面的指令時,會鎖定一個北橋信號,最終來保證操作的原子性
什么是重入鎖,什么是自旋鎖,什么是阻塞
可重入鎖是指允許同一個執行緒多次獲取同一把鎖,比如一個遞回函式里有加鎖操作
自旋鎖不是鎖,而是一種狀態,當一個執行緒嘗試獲取一把鎖的時候,如果這個鎖已經被占用了,該執行緒就處于等待狀態,并間隔一段時間后再次嘗試獲取的狀態,就叫自旋
阻塞,指的是當一個執行緒嘗試獲取鎖失敗了,執行緒就就進行阻塞,這是需要作業系統切換CPU狀態的
你用過JUC中的類嗎,說幾個
Lock鎖體系 ,ConcurrentHashMap ,Atomic原子類,如:AtomicInteger ;ThreadLoal ; ExecutorService
ThreadLocal的作用和原理
ThreadLocal,翻譯成中國話,叫做執行緒本地變數,它是為了解決執行緒安全問題的,它通過為每個執行緒提供一個獨立的變數副本,來解決并發訪問沖突問題 - 簡單理解它可以把一個變數系結到當前執行緒中,達到執行緒間資料隔離目的,
原理:ThredLocal是和當前執行緒有關系的,每個執行緒內部都有一個ThreadLocal.ThreadLocalMap型別的成員變數threadLocals,它用來存盤每個執行緒中的變數副本,key就是ThreadLocal變數,value就是變數副本,
當我們呼叫get方法是,就會在當前執行緒里的threadLocals中查找,它會以當前ThreadLocal變數為key獲取當前執行緒的變數副本
它的使用場景比如在spring security中,我們使用SecurityContextHolder來獲取SecurityContext,比如在springMVC中,我們通過RequestContextHolder來獲取當前請求,比如在 zuul中,我們通過ContextHolder來獲取當前請求
執行緒池的作用
請求并發高的時候,如果沒有執行緒池會出現執行緒頻繁創建和銷毀而浪費性能的情況,同時沒辦法控制請求數量,所以使用了執行緒池后有如下好處
- 主要作用是控制并發數量,執行緒池的佇列可以緩沖請求
- 執行緒池可以實作執行緒的復用效果
- 使用執行緒池能管理執行緒的生命周期
Executors創建四種執行緒池
-
CachedThreadPool:可快取的執行緒池,它在創建的時候,沒有核心執行緒,執行緒最大數量是Integer最大值,最大空閑時間是60S
-
FixedThreadPool:固定長度的執行緒池,它的最大執行緒數等于核心執行緒數,此時沒有最大空閑時長為0
-
SingleThreadPool:單個執行緒的執行緒池,它的核心執行緒和最大執行緒數都是1,也就是說所有任務都串行的執行
-
ScheduledThreadPool:可調度的執行緒池,它的最大執行緒數是Integer的最大值,默認最長等待時間是10S,它是一個由延遲執行和周期執行的執行緒池
執行緒池的執行流程
corePoolSize,maximumPoolSize,workQueue之間關系,
- 當執行緒池中執行緒數小于corePoolSize時,新提交任務將創建一個新執行緒(使用核心)執行任務,即使此時執行緒池中存在空閑執行緒,
- 當執行緒池中執行緒數達到corePoolSize時(核心用完),新提交任務將被放入workQueue中,等待執行緒池中任務調度執行 ,
- 當workQueue已滿,且maximumPoolSize > corePoolSize時,新提交任務會創建新執行緒(非核心)執行任務,
- 當workQueue已滿,且提交任務數超過maximumPoolSize(執行緒用完,佇列已滿),任務由RejectedExecutionHandler處理,
- 當執行緒池中執行緒數超過corePoolSize,且超過這部分的空閑時間達到keepAliveTime時,回收這些執行緒,
- 當設定allowCoreThreadTimeOut(true)時,執行緒池中corePoolSize范圍內的執行緒空閑時間達到keepAliveTime也將回收,
執行緒池執行流程 : 核心執行緒 => 等待佇列 => 非核心執行緒 => 拒絕策略
執行緒池構造器的7個引數
-
CorePoolSize:核心執行緒數,它是不會被銷毀的
-
MaximumPoolSize :最大執行緒數,核心執行緒數+非核心執行緒數的總和
-
KeepAliveTime:非核心執行緒的最大空閑時間,到了這個空閑時間沒被使用,非核心執行緒銷毀
-
Unit:空閑時間單位
-
WorkQueue:是一個BlockingQueue阻塞佇列,超過核心執行緒數的任務會進入佇列排隊
-
ThreadFactory:它是一個創建新執行緒的工廠
-
Handler:拒絕策略,任務超過最大執行緒數+佇列排隊數 ,多出來的任務該如何處理取決于Handler
執行緒池拒絕策略有幾種
拒絕策略,當執行緒池任務超過 最大執行緒數+佇列排隊數 ,多出來的任務該如何處理取決于Handler
- AbortPolicy丟棄任務并拋出RejectedExecutionException例外;
- DiscardPolicy丟棄任務,但是不拋出例外;
- DiscardOldestPolicy丟棄佇列最前面的任務,然后重新嘗試執行任務;
- CallerRunsPolicy由呼叫執行緒處理該任務
可以定義和使用其他種類的RejectedExecutionHandler類來定義拒絕策略,
你知道ScheduledThreadPool使用場景嗎
這是帶定時任務的執行緒池,EurekaClient拉取注冊表&心跳續約就是使用的這個執行緒池,
JVM篇
你們用什么工具監控JVM
jconsule, jvisualvm
JVM類加載流程
loading加載:class檔案從磁盤加載到記憶體中
verification驗證:校驗class檔案,包括位元組碼驗證,元資料驗證,符號參考驗證等等
preparation準備:靜態變數賦默認值,只有final會賦初始值
resolution決議:常量池中符號參考,轉換成直接訪問的地址
initializing初始化:靜態變數賦初始值
JVM類加載器有幾種型別,分別加載什么東西,用到什么設計模式?
-
BootStrap ClassLoader 啟動類加載器,加載<JAVA_HOME>\lib下的類
-
Extenstion ClassLoader 擴展類加載器,加載<JAVA_HOME>\lib\ext下的類
-
Application ClassLoader 應用程式類加載器,加載Classpath下的類
-
自定義類加載器
這里是用到了雙親委派模式,從上往下加載類,在這程序中只要上一級加載到了,下一級就不會加載了,這麼做的目的
- 不讓我們輕易覆寫系統提供功能
- 也要讓我們擴展我們功能,
JVM組成,以及他們的作用
運行時資料區:
-
堆:存放物件的區域,所有執行緒共享
-
虛擬機堆疊:對應一個方法,執行緒私有的,存放區域變數表,運算元堆疊,動態鏈接等等
-
本地方法堆疊:對應的是本地方法,在hotspot中虛擬機堆疊和本地方法堆疊是合為一體的
-
程式計數器:確定指令的執行順序
-
方法區:存放虛擬機加載的類的資訊,常量,靜態變數等等,JDK1.8后,改為元空間
執行引擎:
-
即時編譯器,用來將熱點代碼編譯成機器碼(編譯執行)
-
垃圾收集,將沒用的物件清理掉
本地方法庫:融合不同的編程語言為java所用
在JVM層面,一個執行緒是如何執行的
執行緒執行,每個方法都會形成一個堆疊幀進行壓榨保存到虛擬機堆疊中,方法呼叫結束就回出堆疊,呼叫程序中創建的變數在虛擬機堆疊,物件實體存放在堆記憶體中,堆疊中的變數指向了對中的記憶體,當方法執行完成就出堆疊,創建的變數會被銷毀,堆中的物件等待GC,
程式記憶體溢位了,如何定位問題出在哪兒?
增加啟動引數-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:\ 可以把記憶體溢位的日志輸出到檔案,然后通過JVM監視工具VisualVM來分析日志,定位錯誤所在,在linux服務器也可以使用命令: jmap -dump 來下載堆快照,
垃圾標記演算法
垃圾標記演算法有:參考計數和可達性演算法
- 參考計數 : 給每一個物件添加一個參考計數器,每當
有一個地方參考它時,計數器值加1;每當有一個地方不再參考它時,計數器值減1,這樣只要計數器的值不為0,就說明還有地方參考它,它就不是無用的物件. 這種演算法的問題是當某些物件之間互相參考時,無法判斷出這些物件是否已死 - GC Roots :找到一個物件作為 CG Root , 當一個物件到GC Roots沒有任何參考鏈相連(GC Roots到這個物件不可達)時,就說明此物件是不可用的
垃圾回收演算法
-
標記清除演算法 :分為標記和清除兩個階段,首先標記出所有需要回收的物件,標記完成后統一回收所有被標記的物件 ;缺點:標記和清除兩個程序效率都不高;標記清除之后會產生大量不連續的記憶體碎片,
-
復制演算法 :把記憶體分為大小相等的兩塊,每次存盤只用其中一塊,當這一塊用完了,就把存活的物件全部復制到另一塊上,同時把使用過的這塊記憶體空間全部清理掉,往復回圈 ,缺點:實際可使用的記憶體空間縮小為原來的一半,比較適合
-
標記整理演算法 :先對可用的物件進行標記,然后所有被標記的物件向一段移動,最后清除可用物件邊界以外的記憶體
-
分代收集演算法 :把堆記憶體分為
新生代和老年代,新生代又分為Eden區、From Survivor和To Survivor,一般新生代中的物件基本上都是朝生夕滅的,每次只有少量物件存活,因此新生代采用復制演算法,只需要復制那些少量存活的物件就可以完成垃圾收集;老年代中的物件存活率較高,就采用標記-清除和標記-整理演算法來進行回收,
垃圾回收器有哪些
-
新生代:Serial :一款用于
新生代的單執行緒收集器,采用復制演算法進行垃圾收集,Serial進行垃圾收集時,不僅只用一條執行緒執行垃圾收集作業,它在收集的同時,所有的用戶執行緒必須暫停(Stop The World -
新生代:ParNew : ParNew就是一個Serial的多執行緒版本`,其它與Serial并無區別,ParNew在單核CPU環境并不會比Serial收集器達到更好的效果,它默認開啟的收集執行緒數和CPU數量一致,可以通過-XX:ParallelGCThreads來設定垃圾收集的執行緒數,
-
新生代:Parallel Scavenge(掌握) Parallel Scavenge也是一款用于新生代的
多執行緒收集器,與ParNew的不同之處是,ParNew的目標是盡可能縮短垃圾收集時用戶執行緒的停頓時間,Parallel Scavenge的目標是達到一個可控制的吞吐量.Parallel Old收集器以多執行緒,采用標記整理演算法進行垃圾收集作業, -
老年代:Serial Old ,Serial Old收集器是Serial的老年代版本,同樣是一個單執行緒收集器,采用標記-整理演算法,
-
老年代CMS收集器是一種以最短回收停頓時間為目標的收集器,以“最短用戶執行緒停頓時間”著稱,整個垃圾收集程序分為4個步驟
- 初始標記:標記一下GC Roots能直接關聯到的物件,速度較快
- 并發標記:進行GC Roots Tracing,標記出全部的垃圾物件,耗時較長
- 重新標記:修正并發標記階段參考戶程式繼續運行而導致變化的物件的標記記錄,耗時較短
- 并發清除:
用標記-清除演算法清除垃圾物件,耗時較長
整個程序耗時最長的并發標記和并發清除都是和用戶執行緒一起作業,所以從總體上來說,
CMS收集器垃圾收集可以看做是和用戶執行緒并發執行的, -
老年代:Parallel Old ,Parallel Old收集器是Parallel Scavenge的老年代版本,是一個
多執行緒收集器,采用標記-整理演算法,可以與Parallel Scavenge收集器搭配,可以充分利用多核CPU的計算能力, -
堆收集:G1 收集器, G1 收集器是jdk1.7才正式參考的商用收集器,現在已經成為
jdk1.9默認的收集器,前面幾款收集器收集的范圍都是新生代或者老年代,G1進行垃圾收集的范圍是整個堆記憶體,它采用“化整為零”的思路,把整個堆記憶體劃分為多個大小相等的獨立區域(Region)在每個Region中,都有一個Remembered Set來實時記錄該區域內的參考型別資料與其他區域資料的參考關系(在前面的幾款分代收集中,新生代、老年代中也有一個Remembered Set來實時記錄與其他區域的參考關系),在標記時直接參考這些參考關系就可以知道這些物件是否應該被清除,而不用掃描全堆的資料
Jdk1.7.18新生代使用Parallel Scavenge,老年代使用Parallel Old
Minor GC和Full GC
新生代的回收稱為Minor GC,新生代的回收一般回收很快,采用復制演算法,造成的暫停時間很短 ,而Full GC一般是老年代的回收,并伴隨至少一次的Minor GC,新生代和老年代都回收,而老年代采用標記-整理演算法,這種GC每次都比較慢,造成的暫停時間比較長`,通常是Minor GC時間的10倍以上,盡量減少 Full GC
JVM優化的目的是什么?
優化程式的記憶體使用大小,以及減少CG來減少程式的停頓來提升程式的性能,
堆怎么調,堆疊怎么調
-Xms : 初始堆,1/64 物理記憶體
-Xmx : 最大堆,1/4物理記憶體
-Xmn :新生代大小
-Xss : 堆疊大小
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/387059.html
標籤:其他
上一篇:Mac安裝git
下一篇:第二講 外部物體注入
