Java面試相關知識點
這篇筆記主要用來記錄一個大牛的java相關知識點
一、計算機網絡
網絡上的知識,基本上以理論為主
1. OSI七層協(xié)議
物理層:用于信號傳輸,進行數模轉換/模數轉換,基本數據是:比特,網卡在這一層工作
數據鏈路層:定義格式化數據進行傳輸和為物理介質的控制,提供數據校驗,基礎數據是:幀,交換機在這一層工作
網絡層:將網絡地址翻譯成物理地址并且決定傳輸的路徑,基本數據是:數據包,路由器在這里工作(TCP/IP協(xié)議就在此層)
傳輸層:解決數據傳輸的穩(wěn)定性,必要時將數據分割(TCP/UDP協(xié)議就工作在此層)
會話層:解決不同操作系統(tǒng)的通訊語法的問題
應用層:(HTTP協(xié)議就工作在此層)

2. TCP/IP協(xié)議族的四層協(xié)議
OSI七層協(xié)議知識一個概念性的框架,TCP/IP協(xié)議族是OSI七層模型的實現(xiàn),但是它并沒有完全按照OSI七層模型進行實現(xiàn)。

3. TCP和三次握手
IP協(xié)議是無鏈接的通訊協(xié)議,但是IP協(xié)議沒有控制包的順序和可靠性,因此需要上層的TCP協(xié)議進行控制。
TCP將上層的數據報文分割成適當大小的包交給網絡層進行發(fā)送,為了保證順序和可靠,TCP會對每個包進行編號,并且需要接收方發(fā)送ACK進行確認來確保不會丟包
3.1 TCP報文頭

3.2 三次握手

相關的標識位如下:
- ACK:缺人需要表示1:確認有效 0:不含確認信息
- SYN:同步需要 SYN=1且ACK=0表示沒有捎帶確認的信息 SYN=1且ACK=1表示會稍帶確認上一條數據
- FIN:結束標志
握手過程:
- C端發(fā)出SYN包(seq = x)請求建立鏈接,進入SYN-SENT狀態(tài)
- S端收到剛剛的SYN包,發(fā)出確認包(ack = x+1),并且自己也發(fā)出一個SYN包(seq = y),請求確認,進入SYN-REVD狀態(tài)
- C端收到S端的確認包和SYN包,再發(fā)送一個確認包(ack = y+1),這是進入ESTAB-LISHED狀態(tài),S端收到后也進入ESTAB-LISHED狀態(tài),開始傳輸數據
注意:第二次握手時S端收到首次發(fā)送C端的SYN包,發(fā)送確認包和自己的SYN包時如果S端沒有收到ACK確認時,S端會不斷重試,linux默認情況下會等待63秒
4.四次揮手

- 揮手過程
- C端發(fā)送FIN包(seq = u)請求關閉鏈接,進入FIN-WAIT1狀態(tài)
- S端收到FIN包,并且發(fā)送確認包(ack = u+1),進入CLOSE-WAIT狀態(tài),C端收到后進入FIN-WAIT2狀態(tài),等待S端發(fā)送最后的數據
- S端完成最后數據的發(fā)送后,發(fā)送FIN包(seq = w),并且再次確認(ack = u+1),發(fā)送后S端進入LAST-ACL狀態(tài)
- C端收到S端發(fā)送的FIN包后,發(fā)送確認包(ack = w+1),自己進入TIME-WAIT狀態(tài),經過兩個延遲時間后正式關閉鏈接,S端收到后也會直接關閉鏈接
- 為啥需要有TIME-WAIT狀態(tài)
- 保證S端收到最后發(fā)送的ACK包,并且保證不會和后面的鏈接混淆
- 頻繁出現(xiàn)大量CLOST-WAIT的原因
- 在客戶端應用層關閉Socket后,服務器忙于讀寫沒有進一步發(fā)送第二個FIN包導致大量CLOST-WAIT積壓
5.UDP和TCP的區(qū)別
5.1 UDP的特點
- UDP時非鏈接的,不支持錯誤重傳,華東窗口等,只會盡可能快的傳入網絡中
- 不用維護鏈接狀態(tài),可以同時想多個客戶端傳輸一樣的信息
- UDP不保證可靠,保證速度
- UDP時面向報文的,不具備有序性,所有的事情會交給上層應用處理
6. TCP的滑動窗口
TCP的滑動窗口是為了解決TCP的流浪控制和亂序重排的問題,未來研究這個問題,我們先知道下面這兩個概念
RTT:發(fā)出需要確認的包到收到確認包到時間
RTO:重傳時間間隔(確認包的超時時間)
6.1 滑動窗口過程

每次收到接受放確認的連續(xù)包后,窗口會進行滑動,框入更多的未發(fā)送數據
7. HTTP協(xié)議
主要特點:
- 支持客戶/服務器模式
- 簡單快速,只要發(fā)送請求方法和路徑即可
- 無鏈接的,傳輸完數據就會斷開鏈接
- 無狀態(tài)的,兩次鏈接的狀體對方不可感知
7.1 HTTP請求/響應結構
- 請求報文結構如下

- 響應報文結構如下

7.2 Cookie和Session
- Cookie:服務器要求客戶端保存的信息
- Session:根據客戶端發(fā)送的session-id來在服務器中檢索session信息
- 使用cookie保存session-id
- 使用url參數保存session-id
7.3 HTTPS
加入ssl層,加入安全套接層,提供安全和數據完整性的協(xié)議,通過身份驗證和數據加加密的完整性
- 加密的方式
- 對稱加密:加密和解密使用同一個密鑰,性能較高
- 非對稱加密:加密使用公鑰,解密使用私鑰
- 哈希算法
- 數字簽名:證明某個消息是某人發(fā)送的,沒有被篡改的
- HTTPS流程
- 瀏覽器發(fā)送支持的加密算法發(fā)送服務器
- 服務器選擇一套瀏覽器支持的算法,發(fā)送相關證書給瀏覽器
- 瀏覽器驗證證書合法性,并且使用證書中的公鑰加密傳輸用的對稱加密對稱加密的密碼,發(fā)給服務器
- 服務器使用私鑰解密瀏覽器發(fā)送的對稱加密密碼
二、 數據庫
1. 數據庫設計和模塊劃分
如果我沒來設計數據庫,需要這樣幾部分
- 存儲模塊:將數據存入磁盤
- 存儲管理:將物理數據組織和表述
- 優(yōu)化存儲效能?一次性讀取多行減少IO次數
- 緩存機制
- SQL接信息模塊
- 日志管理:管理操作日志
- 用戶權限模塊
- 異常容災難機制
- 索引模塊
- 鎖模塊
2. 索引模塊
為什么要使用索引?
相當于字典的目錄,通過索引可以大幅提升查詢速度
2.1 索引的數據結構
-
二叉查找樹
二叉樹.png二叉查找樹:對于樹中的任意節(jié)點,比左子樹的值大,比右子樹的值小??
平衡二叉樹:根結點的左右子樹高度相差不超過1
查找效率:O(logn),但是最多兩個孩子的設定會大大增加樹的深度,增加查找次數
-
B樹
3階段B樹B樹可以通過策略保證新增節(jié)點不會變成線性的(B樹節(jié)點的分裂,合并)
-
B+數(MySQL使用這種數據結構作為索引)
B+樹B+樹的數據全部存儲在葉子結點上,非葉子結點只用于索引,葉子結點的鏈表結構也便于做數據統(tǒng)計和全盤掃描
-
Hash結構
相當于Java的HashMap的索引方式,只需要O(1)的時間
但是無法排序、范圍操作,可能同一個哈希插入太多值造成效率不如哈希
2.2. 密集索引和稀疏索引的區(qū)別
密集索引:每一個搜索碼都對應一個搜索值(只能有一個,相當于組件,相當于葉子結點保存了所有的信息)
-
稀疏索引:只為了索引某些相才建立的索引(葉子結點里只保存了數據的存儲地址)
在MySQL的MyISAM引擎里所有的索引都是稀疏索引
MySQL的InnoDB引擎的索引有且僅有一個密集索引(主鍵 ==> 第一個唯一非空索引 ==> 生成隱藏的索引 )
因此在使用InnoDB的稀疏索引時會先找到對應的密集索引,再通過密集索引找到數據(兩次索引)
2.3 SQL衍生問題
-
如何優(yōu)化慢SQL
-
根據慢日志定位問題:修改mysql中的變量來開啟慢日志(下面的操作在my.ini更改后就會永久保存)
shwo variables like 'query' #找到和查詢有關的全局變量 set global show_query_log = no #開啟慢日志 set global long_query_time = 1 #設置慢查詢時間為1秒 show status like '%slow_queries' #查看慢sql個數 -
利用
explan來分析select查詢,用法就是在select前面加上explan就好了關注出現(xiàn)的
type字段,如果是“index”或者"all"就表明查詢是通過全表掃描完成的,需要優(yōu)化-
關注
extar字段[圖片上傳失敗...(image-52b86-1584189158882)]
修改SQL讓其盡量使用索引或者添加索引
-
-
聯(lián)合索引最左原則
最左匹配原則:A、B字段組成聯(lián)合索引,當
where A = xxx和where A = xxx and B = xxx時,會使用這個聯(lián)合索引,但是當where B = XXX時,就不會使用這個索引了-
原因:聯(lián)合索引建立時,會先對第一個字段進行排序,在這個順序的基礎上再對第二個字段進行陪許,因此第二個字段是局部有序的
最左匹配
-
索引是越多越好嗎
索引并不是越多越好。索引也需要維護并且占用空間。
3. 鎖模塊
3.1 MyISAM和InnoDB鎖的區(qū)別
MyISAM默認表級鎖,不支持行級鎖
-
InnoDB默認行級鎖:在使用索引時使用行級鎖,不使用索引時使用表鎖
兩種引擎適合的場景:
MyISAM:頻繁執(zhí)行全表count、查詢操作較多(只有表鎖,效率低),寫的操作較少、不需要事務
InnoBD:增刪改查都很頻繁的系統(tǒng)、穩(wěn)定性要求較高
3.2 鎖的分類
- 鎖的粒度劃分:表級鎖,行級鎖,頁級鎖
- 鎖的級別劃分:讀鎖 = 共享鎖,可以反復加上 寫鎖=獨占鎖,只能加上一次,且不能和讀鎖共存
- 加鎖的方式劃分:自動鎖,顯式鎖
- 按操作劃分:DML鎖:對數據進行加鎖 DDL鎖:對表結構枷鎖
- 使用方式劃分:悲觀鎖:先取鎖,再訪問,樂觀鎖:只有提交更新時才會檢測是否沖突
3.3 數據庫事務的四大特性
- A:原子性:對于事務中的操作,要么全執(zhí)行,要么全不執(zhí)行(回滾)
- C:一致性:數據庫中的數據在事務執(zhí)行前后應保存一致
- I:隔離性:多個事務的并發(fā)執(zhí)行不會相互影響(隔離等級)
- D:持久性:事務一旦提交,對系統(tǒng)的修改應該永久保存
3.4 事務并發(fā)訪問產生的問題和隔離級別
-
事務并發(fā)訪問可能引起的問題
-
丟失修改:一個事務的更新覆蓋了另一個事務的更新(在任何隔離級別(讀未提交)下都可以防止)
丟失修改 臟讀:讀取到還未提交的事務修改的數據(可以在讀已提交級別防止,該級別不允許讀未提交的數據)
不可重復讀:事務A多次讀取同一個數據,事務B在A讀取時作出更新并提交導致A在多次讀取時內容不一致(可以在可重復讀級別防止,該級別確保別的事物的提交不會更改本次事務查詢的結果)
幻讀:事務A多次讀取若干行,事務B進行插入或者刪除操作導致事務A多次讀取的數據的數量不一致(可在Serializable級別防止,這個級別會讓事務串行提交)
事務隔離級別越高,事務的并發(fā)度越低,因此需要根據需求來確定隔離級別
在MySQL的InnoDB中如何在可重復讀中避免了幻讀的?
MySQL中存在兩種讀取數據的方式:當前讀和快照讀
- 當前讀:selete ... lock in share mode、delete、update、insert操作都為當前讀,當前讀會獲取數據的最新版本
- 快照讀:不加鎖的讀selete為快照讀,在“可重復讀”的隔離級別下,讀到的不是數據的最新版本。在“可重復讀”的級別下,快照讀讀出的都是第一次執(zhí)行這條語句的數據。
-
4. SQL關鍵語法
假設我們的表結構如下

4.1 Group By
根據查詢結果進行分組查詢,對每個組的數據進行聚合運算。
例子:查詢所有同學的學號,姓名,選課數,和每個同學成績的總和
selete s.student_id,stu.name,count(s.coures_id),sum(s.score)
form score s,student stu
where s.student_id = stu.student_id
greop by student_id
4.2 Having
通常于Group By一起使用,給Group By 添加限定條件
例子: 查詢所有平均分大于60分的同學的學號,姓名,選課數,和每個同學的平均成績
selete s.student_id, stu.name, count(s.coures_id), avg(s.score)
form score s,student stu
where s.student_id = stu.student_id
greop by student_id
having avg(s.score) > 60
例子:查詢使用沒有學全所有課程的學號和姓名
selete s.student_id, stu.name
from score s, student stu
where s.student_id = stu.student_id
group by s.student_id
having count(*) < (
select count(*) from course
)
三、Redis
- Redis快的原因
- Redis是完全基于內存的
- 數據結構簡單
- 采用單線程和IO多路復用,不存在上下文切換問題
1 IO多路復用模型
- Select系統(tǒng)調用:可以監(jiān)控多個IO的文件描述符,只用一個線程就可以完成多個IO的操作
2 Redis數據類型
- String:最基礎的數據模型,是包括二進制的
- Hash:String元素組成的字典,可以方便的存入POJO
- List:String元素組成的隊列,有序可重復
- Set:String元素組成的集合,無序不重復,集合可以交集并集的操作
3 相關問題
3.1 如何從redis中查詢固定前綴的簡直對
使用keys指令:keys會一次性獲取所有參數,數據過大時可能會使服務器卡頓
-
使用Scan命令,相當于給返回數據分頁,每次查詢可以從上次結束的地方繼續(xù)查詢,但是可能會獲取到重復的問題,而且給的的count并不會完全符合
scan [開始行] match [匹配模式] count [個數]
3.2 實現(xiàn)分布式鎖
- 使用
SETNT [key] [value]來設置一個值,設置成功返回1,失敗返回0且不會被更改,再使用EXPIRE [key] [seconds]來設置過期時間「這種模式并沒有原執(zhí)性,使用set [key] [value] ex [seconds]來保證原子性」
3.3 避免同一時間大量的key過期
過期時間需要添加隨機值
3.4 redis做一步隊列(消息隊列)
用list數據的pop操作和push的操作
- 使用
BLPOP [key] [超時時間]來達到監(jiān)聽的效果 - 使用pub/sub主題訂閱者模式做消息訂閱的效果,用
publish [topic] [msg]來發(fā)布消息,用subscribe [topic]來接受相關的消息,甚至不用創(chuàng)建頻道。但是這個狀態(tài)是無狀態(tài)的,不保證送達,如果有更高的需求,就要使用專業(yè)的消息隊列了
4 Redis持久化方式
4.1 RDB持久化
會在固定的間隔保存一次整個數據庫的全量數據。使用save指令在主線程中進行RDB備份,使用bgsave指令使用fork的子線程完成備份
4.2 AOF持久化
相當于備份操作日志,每一個和寫入有關的操作會被記錄到一個文件中
四、Java相關知識點
1. ClassLoader
1.1 類從編譯到執(zhí)行的過程
- 編譯器把.java文件編譯成.class字節(jié)碼文件
- ClassLoader將直接碼讀入內存中,轉換為JVM中的Class對象
- JVM再使用Class對象將其實例化為對應的對象
1.2 ClassLoader
在JVM中,所有的類對象都是由ClassLoader進行加載的,主要功能是從class文件中的二進制數據流加載類對象,是Java的核心組件
-
ClassLoader的種類
BootStrapClassLoader:是java的核心庫,是C++編寫的
ExtClassLoader:Java編寫的,用于加載擴展庫
APPClassLoader:Java編寫,加載classpath下相關的文件
-
自己定義的ClassLoader:用于定制化加載,只要傳入的二進制流,就可以進行類的加載(從壓縮包獲取,從網絡中獲取等等)
package com.libi.classloader; /** * @author :Libi * @version :1.0 * @date :2020-03-05 20:56 */ public class MyClassLoader extends ClassLoader { private String path; private String classLoaderName; public MyClassLoader(String path, String classLoaderName) { this.path = path; this.classLoaderName = classLoaderName; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] b = loadClassData(name); return defineClass(name, b, 0, b.length); } //從文件中讀出class數據流,我這邊不想寫了 private byte[] loadClassData(String name) { return null; } //如何通過自定義的ClassLoader進行類的加載 public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { //創(chuàng)建ClassLoader MyClassLoader myClassLoader = new MyClassLoader("path", "name"); //生成類對象 Class c = myClassLoader.loadClass("name"); //生成實例 Object o = c.newInstance(); } }
-
ClassLoader的雙親委派機制
雙親委派機制ClassLoader中,加載類的部分源碼如下
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 首先檢查這個類是否被加載 Class<?> c = findLoadedClass(name); if (c == null) { //如果沒有被加載那么委托它的父加載器,父加載器為空則委托Bootstrap加載器 long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // 如果父加載器仍然沒有找到,就自己找 long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } } -
為什么要使用雙親委派機制?
避免一個類被加載多次,節(jié)約內存
1.3 類裝載方式
類裝改過程如下

- 隱試加載:new(順帶獲取實例)
- 顯示加載:
- ClassLoader.loadClass:還沒有完成鏈接,只完成加載
- Class.forName:已經完成初始化
2. JVM內存模型
2.1 內存模型劃分的區(qū)域
從線程的角度分類

- 程序計數器:相當于行號指示器,是線程獨立的
- Java虛擬機棧:相當單個線程于虛擬機執(zhí)行的調用棧,每個棧幀包括局部變量表,關鍵數,方法出口等信息
- 本地方法棧:調用本地方法是就會使用到本地方法棧
- 元空間(類似之前的永久代,元空間使用本地內存,但是永久代使用的是java管理的內存)
- 字符串常量池存(現(xiàn)在代隊內存中)在永久代中可能出現(xiàn)性能問題
- 簡化GC的操作
- 永久代的大小指定不再成為問題
- Java堆
2.2 JVM常見問題
- JVM性能調優(yōu)參數
- -Xss:每個線程虛擬機棧的大小
- -Xms:堆的初始大小
- -Xmx:堆的最大大小
- 內存分配策略
- 靜態(tài)存儲:編譯時就可以確定的空間需求
- 棧式存儲:編譯時未知,但是但是允許開始前夕可以確定
- 堆式存儲:運行時都無法確定使用的內存大小,完全動態(tài)分配
3. Java垃圾回收機制
3.1 判定為垃圾的算法
當一個java對象沒有被任何對象引用時就會判定為垃圾
-
引用計數法
- 任何對象里有引用計數器,當引用計數為0時,就會判定為垃圾
- 優(yōu)點:速度很快,不會影響程序、缺點:無法辨別循環(huán)引用的情況
-
可達性分析
-
通過對象的引用鏈來判斷是否可以從GC Root進行查找
- GC Root:虛擬機棧、方法區(qū)的常量和靜態(tài)引用的對象
- JNI引用的對象
- 活躍的線程對象
可讀性分析
-
3.2 垃圾回收算法
-
標記清除算法
- 標記:掃描整個GC root,對存活的對象進行標記。清除:對整個堆進行掃描,清除不可達對象
標記清除- 缺點:可能產生大量碎片空間導致無法利用
-
復制算法
-
把整個堆空間分為對象面和空閑面,每次在創(chuàng)建對象在對象面,進行GC時把可用的對象復制到另一邊
復制算法 解決的空間的碎片化問題,且適用于存活率較低的情況。但是會浪費50的空間,若存活率較高,則會產生大量復制操作,浪費時間。
-
-
標記整理算法
-
標記:掃描整個GC root,對存活的對象進行標記。整理:移動存活的對象,移動到前端,再清除后面的所有的空間
標記整理 - 避免了空間的碎片,適用于存活率高的場景
-
標記:掃描整個GC root,對存活的對象進行標記。整理:移動存活的對象,移動到前端,再清除后面的所有的空間
3.3 分代收集算法
對內存進行分類,不同的空間使用不同的手機算法

-
新生代的劃分
- Eden區(qū):新創(chuàng)建的對象出生的區(qū)域
- 兩個Survivor區(qū)
GC會先去清理Eden區(qū)域,把存活的對象放入Survivor區(qū)域,Survivor區(qū)域使用復制算法進行手機。當Survivor區(qū)域的對象連續(xù)存活一定時間后,將會進度老年代。
當較大的對象會直接進入老年代
-
進入老年代的情況
- 在Survivor區(qū)域存活次數超過一定數字時
- Survivor區(qū)域裝不下時
- 新生成的大對象
-
GC的分類
Minor GC:用于新生代的GC
Full GC: 用于老年代的GC,通常也會對新生代進行GC
出發(fā)FullGC的條件
- 老年代空間不足
- 永久代空間不足
- 預計下次MinorGC需要晉升到老年代的大小大于老年代的平均空間
- 調用System.gc()
3.4 新生代的垃圾收集器
-
相關概念
- Stop-the-World:JVM在執(zhí)行GC時會停掉除了GC線程意外的所有線程
- Safepoint:分析引用關系不會發(fā)生變化的時間點,只有程序執(zhí)行到safepoint,gc才會停掉工作線程。如:循環(huán)跳轉,方法調用等時間點
Serial收集器:單線程收集器,必須停掉所有線程

- ParNew收集器:多線程收集的Serial

- Parallel Scavengel收集器:面向吞吐量的GC,會盡可能后臺完成任務
- 吞吐量 = 運行時間/運行時間+GC停下來的時間
3.5 老年代收集器
- Serial Old收集器:單線程標記整理

- Parallel Old收集器:多線程的標記整理

-
CMS收集器:可以后臺運行一部分工作,使用標記清除算法
CMS

- G1收集器
- 是并行和并發(fā)的收集器
- 可以分代收集
3.6 相關問題
-
Java的finalize()方法有什么用
這個方法會在將要被收集前調用,作用是讓其有機會再次和GC Root鏈接(給其重生的機會)
-
Java的強引用,弱引用,弱引用,虛引用
- 強引用:寧可拋出OOM也不會釋放掉還在被強引用的對象
- 軟引用:當內存不足時,即使對象還在被弱引用,這個對象也會被回收,用于高速緩存,可用于引用隊列
- 弱引用:每次GC都會被回收,但是因為GC線程優(yōu)先級低,所以也不會被很快回收,可以配合引用隊列
- 虛引用:對GC來說和沒有引用是一樣的,用于跟蹤對象的生命周期,必須配合引用隊列
4. Java并發(fā)編程
4.1 java線程相關
-
進程和線程的區(qū)別
進程是資源分配的最小單位,線程是cpu調度的最小單位。線程并沒有被看作獨立應用,只有獨立的堆棧和局部變量,沒有獨立的地址空間
線程和進程 -
如何給run()方法傳遞參數
- 構造函數傳參
- 成員變量傳參
- 回調函數傳參
-
如何處理線程的返回值
- 主線程等待:使用
Thread.join()的方法阻塞當前線程 - 通過Callable接口實現(xiàn):
- 通過
FutureTask類,傳入Callable接口的實例,調用call()方法和get()方法,就可以獲取到返回值 - 通過線程池的
submit()方法傳入神經Callable接口的實例,通過返回的Future實例進行獲得返回值
- 通過
- 主線程等待:使用
-
線程的狀態(tài)
- 新建(new):創(chuàng)建后還沒有啟動的狀態(tài)
- 運行(Runnable):線程正在運行或者等待cup時間
- 等待(Waiting):無限期等待,這個線程正在等待被喚醒
- 有限等待(Time Waiting):一定時間后會被系統(tǒng)自動喚醒
- 阻塞(Blocked):等改獲取鎖或者等待io
- 結束(Terminated):已經終止的線程,線程已經執(zhí)行完成,不能再次運行
線程的狀態(tài) -
sleep()和wait()方法- wait方法用于讓出鎖(調用成為鎖的對象的wait方法),可以讓其他等待資源的線程獲得鎖
- sleep方法只會讓出cpu,鎖的狀態(tài)不會改變
-
notify()和notifyAll()-
notifyAll會喚醒全部等待這個鎖的線程,而notify只會喚醒一個 - 這兩個方法都不會釋放自己的鎖
-
-
Thread.yiele()方法當前線程愿意讓出cpu使用權,但是是否讓出還是取決于調度器
-
如何中斷線程
- 已經被廢棄的
stop()方法,這個方法會暴力地停下線程,導致后續(xù)清理的問題 - 調用
interrupt()方法,希望線程結束。- 如果這時線程處在阻塞、等待的狀態(tài),那么就會馬上拋出
InterruptedException異常 - 如果線程處于正?;顒拥臓顟B(tài),也只會把它的中斷標志設置為true,線程仍會繼續(xù)運行,至于要如何中斷,需要自己實現(xiàn)
- 如果這時線程處在阻塞、等待的狀態(tài),那么就會馬上拋出
- 已經被廢棄的
4.2 java中的鎖
-
synchronized的底層對鎖的優(yōu)化
-
自適應自旋鎖
- 自旋:大部分情況下,線程只需要很短的時間就可以獲取鎖,這時進行線程狀態(tài)切開銷較大。因此通過讓線程自行忙循環(huán)等待鎖是否
- 自適應自旋:自旋嘗試的次數不再固定,由上一個獲取到鎖的自旋次數和現(xiàn)在擁有者的狀態(tài)來決定。如果上一次線程是通過自旋獲取到鎖的,而且線程正在運行中,jvm就會認為自旋獲取到鎖的幾率很大,就會增加自旋嘗試次數
鎖消除:JIT編譯時,會對上下文掃描,消除沒有競爭的鎖,避免不必要的開銷
-
鎖粗化:如果對一個對象頻繁進行加鎖,也會消耗不少的資源,這時就會進行鎖的粗化,擴大鎖的范圍
鎖的狀態(tài):無鎖 ==> 偏向鎖 ==> 輕量級鎖 ===> (自旋鎖?) ===> 重量級鎖
偏向鎖:大多數情況總是一個線程獲得了鎖,因此一個線程獲得鎖后,鎖就變成了偏向模式,該線程下一次獲取鎖事,不需要再做任何同步的操作就會獲得鎖。但是對于鎖競爭比較激烈的場合并不適用
輕量級鎖:當第二個線程開始爭奪鎖后,鎖的模式就升級成的輕量級模式。如果線程是交替執(zhí)行同步快的時候,輕量級鎖就會啟動
重量級鎖:當有鎖不再是兩個線程交替執(zhí)行而是開始等待時,鎖就會變成重量級鎖
-
-
synchronized和ReentrantLock(重入鎖)的區(qū)別
- ReentrantLock是JUC包下的鎖類,實現(xiàn)了多種實用的方法,基于AQS實現(xiàn)的
- ReentrantLock一定要顯示的釋放鎖,不然會導致該線程一直持有鎖
- ReentrantLock有比synchronized實現(xiàn)了更多的功能
- 實現(xiàn)公平鎖(給鎖的順序傾向于給等待時間最長的線程,比非公平鎖性能降低)
- 可以設置獲取鎖的超時時間
- 判斷是否有線程或特定線程是否在嘗試獲取該鎖
- 判斷是否可以獲得該鎖
- ReentrantLock的
newCondition()方法獲取的對象有awit()和singal()方法實現(xiàn)等待和通知操作 - synchronized使用的是操作對象頭中的MarkWord,ReentrantLock使用的是Unsafe類下的
park()方法
4.3 JMM如何解決可見性的問題
-
Java內存模型(JMM):只是一個規(guī)范,它規(guī)定了程序中的變量訪問內存的方式
image.png- 所有的變量都存儲在主內存中,所有的內存都可以共享訪問。
- 線程對變量的操作只能在工作內存中執(zhí)行,線程把要操作的數據拷貝到自己的工作內存中,運行完成后再寫回主內存,不會直接操作主內存
-
多線程共享內存可能會導致數據一致性的問題,因此JMM會對一些操作進行優(yōu)化
指令重排序:如果兩個操作無法通過happens-before原則推導出來,才可以進行重排序
-
happens-before原則:A操作如果需要對B操作可見,則A,B存在happens-before原則。happens-before原則具體如下圖
image.png如果兩個操作不滿足上述任何一個規(guī)則,那么這兩個操作就沒有執(zhí)行順序上的保障,JVM會對這兩個操作進行重排序。反之,如果A happens-before B,那么A的操作在B上就是可見的
-
volatile關鍵字:
- 保障被其修飾的共享變量在所有線程中是可見的,對這個變量的任何改動都會立即反應到共享內存中
- 禁止指令的重排序
- 并不能保障操作的原子性,因此不能完全保障并發(fā)安全
-
volatile如何保障立即可見
- 被volatie修飾的變量被改變時,JMM會立即刷新到主內存中
- 每讀一個volatile變量,JMM會對對應的工作內存置為無效,線程只能從主內存中讀取
-
volatile如何保障禁止重排序
- 插入內存屏障
4.4 樂觀鎖和CAS操作
- JUC包下提供了atomic包提供了很多原子性的數據類型和引用
- 缺點:
- 如果循環(huán)時間太長,開銷很大
- 只能保障一個變量的原子性操作
- ABA的問題
4.5 Java線程池
利用Executors提供的線程池可以滿足不同場景的需求

-
使用線程池的原因
- 減少創(chuàng)建線程的消耗,復用線程
- 統(tǒng)一管理,調優(yōu)和監(jiān)控
-
JUC的有關線程池的接口
-
Executor:只有
executor()的方法,只是用于和任務提交和執(zhí)行細節(jié)解耦public interface Executor { void execute(Runnable command); } -
ExecutorService:擴展了Executer,提供了管理器和生命周期的方法
public interface ExecutorService extends Executor { void shutdown(); List<Runnable> shutdownNow(); boolean isShutdown(); boolean isTerminated(); boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException; //后面的代碼略過 } ScheduledExecutorService:擴展ExecutorService,增加了Future和定時任務
-
-
ThreadPoolExecutor線程池
線程池- 線程池相關的參數
- corePoolSize:核心線程數,長期駐留的線程數量
- maximumPoolSize:線程可以創(chuàng)建的最大線程數量
- woreQueue:當核心線程滿了之后任務等待的隊列。我們使用不同的隊列,就會有不同的排列機制
- keepAliveTime:當核心線程以外的線程閑置以后可以存活的時間
- ThreadFactory:用于創(chuàng)建線程
- handler:任務的拒絕策略
- AbortPolicy:直接拋出異常
- CallerRunsPolicy:使用調用者的線程執(zhí)行任務
- DiscardOldestPolicy:丟棄阻塞隊列中最前的任務并且執(zhí)行本任務
- DiscardPOlicy:直接丟棄任務
- 可也以自己實現(xiàn)RejectedExecutionHandler接口來自定義handler
- 線程池中對任務的處理:
- 如果當前線程數小于核心線程數,則創(chuàng)建一個線程執(zhí)行(即使其他線程空閑)
- 如果當前任務大于核心線程數,則封裝后進入等待隊列
- 如果等待隊列已滿,則創(chuàng)建臨時線程執(zhí)行任務
- 若當前線程數大于最大線程數,且阻塞隊列已經滿了,則通過handler定義的策略處理任務
- 線程池的運行狀態(tài)
- Running:正在運行,線程池可以提交新的任務
- Shutdown:正在停止,線程池不再接受新的任務,但是還會處理隊列中的任務,調用線程池的
shutdown()方法會使線程池進入這個狀態(tài) - Stop:完全停止,線程池不再接受新的任務,也不處理隊列中的任務,線程池中的線程會被中斷,調用線程池的
shutdownNow()方法或者shutdown狀態(tài)的線程池執(zhí)行完剩余任務會使線程池進入和這個狀態(tài) - Tydying:所有的任務都已經終止了
- Terminated:
- 線程池相關的參數















