【java面試】線程篇
- 一、基礎知識
- 1 線程與進程的區別
- 2 并行與并發的區別
- 3 創建線程的方式
- 4 線程包含了哪些狀態,狀態之間是如何變化的?
- 5 新建三個線程,如何保證他們按照順序執行?
- 6、java中的wait和sleep方法的不同
- 7 如何停止一個正在運行的線程
- 二、線程并發安全
- 1 synchronized關鍵字的底層原理
- 1.1 synchronized關鍵字的使用
- 1.2 Monitor
- 1.3 總結
- 2 synchronized關鍵字的底層原理--進階
- 1.1 Monitor重量級鎖
- 1.2 輕量級鎖
- 1.3 偏向鎖
- 1.4 總結
- 3 JMM(java memory Model--Java內存模型)
- 4 CAS
- 4.1 基本概念
- 4.2 底層實現
- 4.3 樂觀鎖和悲觀鎖
- 4.4 總結
- 4 volatile
- 4.1 保證線程間的可見性
- 4.2 volatile禁止指令重排序
- 4.3 總結
- 5 AQS
- 5.1 基本概念
- 5.2 基本工作機制
- 5.3 總結
- 5 ReentrantLock實現原理
- 7 synchronized與Lock有什么區別?
- 8 死鎖產生的條件
- 8.1 基本概念
- 8.2 死鎖診斷
- 9 ConcurrentHashMap
- 10 導致并發程序出現問題的根本原因
- 三、線程池
- 1 線程池的核心參數
- 1.1 核心參數
- 1.2 執行原理
- 2 線程池中常見阻塞隊列
- 3 如何確定核心線程數
- 4 線程池種類
- 5 為什么不建議Executor創建線程池
- 四、使用場景
- 1 線程池使用場景
- 2 es數據批量導入
- 3 數據匯總
- 4 異步調用
- 5 如何控制某個方法允許并發訪問的數量
- 6 談談對TreadLocal的理解
- 6.1 set方法
- 6.2 get方法/remove方法
- 6.3 ThreadLoca內存泄漏問題

一、基礎知識
1 線程與進程的區別
①進程
程序由指令
和數據
組成,但這些指令要運行,數據要讀寫,就必須將指令加載至CPU,數據加載至內存。在指令運行過程中還需要用到磁盤、網絡等設備。進程就是用來加載指令、管理內存、管理IO的。
當一個程序被運行,從磁盤加載這個程序的代碼至內存,這時就開啟了一個進程
。
②線程
一個線程就是一個指令流,將指令流中的一條條指令以一定的順序交給CPU 執行,一個進程之內可以分為一到多個線程。
③二者區別
- 進程是正在運行程序的實例,進程中包含了線程,每個線程執行不同的任務
- 不同的進程使用不同的內存空間,在當前進程下的所有線程可以共享內存空間
- 線程更輕量,線程上下文切換成本一般上要比進程上下文切換低(上下文切換指的是從一個線程切換到另一個線程)
2 并行與并發的區別
- 并發(concurrent)是同一時間應對(dealing with)多件事情的能力
- 并行(parallel)是同一時間動手做(doing)多件事情的能力
3 創建線程的方式
①繼承Thread類
②實現runable接口
③實現Callable接口(適用于有返回值的)
④線程池創建線程
開啟線程的方法?
- 繼承Thread類
- 實現runnable接口
- 實現Callable接口
- 線程池創建線程(項目中使用方式)
使用tunable和callable都可以創建線程,它們之間有什么區別?
- Runnable接口run方法沒有返回值
- Callable接口call方法有返回值,是個泛型,和Future、FutureTask配合可以用來獲取異步執行的結果
- Callable接口的call()方法允許拋出異常;而Runnable接口的run()方法的異常只能在內部消化,不能繼續上拋
在啟動線程時可以使用run方法嗎?run()和start()有什么區別?
- start():用來啟動線程,通過該線程調用run方法執行run方法中所定義的邏輯代碼。start方法只能被調用一次。(線程只能被開啟一次)
- run():封裝了要被線程執行的代碼,可以被調用多次。
4 線程包含了哪些狀態,狀態之間是如何變化的?
線程之間包含了那些狀態?
新建(NEW) 、可運行(RUNNABLE)、阻塞(BLOCKED)、等待( WAITING )、時間等待(TIMED_WALTING)、終止(TERMINATED)
狀態之間如何轉換?
5 新建三個線程,如何保證他們按照順序執行?
join{}:等待線程結束
例如:t.join(),阻塞調用次方的的線程進入time_waiting,直到線程t完成后,此線程在繼續執行。
notify()和notifyAll()有什么區別?
- notifyAll():喚醒所有wait的線程
- notify():只隨機喚醒一個wait線程
6、java中的wait和sleep方法的不同
7 如何停止一個正在運行的線程
二、線程并發安全
1 synchronized關鍵字的底層原理
1.1 synchronized關鍵字的使用
Synchronized【對象鎖】采用互斥的方式讓同一時刻至多只有一個線程能持有【對象鎖】,其它線程再想獲取這個【對象鎖】時就會阻塞住
未加鎖:出現超賣的情況
加鎖后:一個線程在扣票的過程中,另一個線程是無法去獲取的
1.2 Monitor
- Owner:存儲當前獲取鎖的線程的,只能有一個線程可以獲取
- EntryList:關聯沒有搶到鎖的線程,處于Blocked狀態的線程
- WaitSet:關聯調用了wait方法的線程,處于Waiting狀態的線程
1.3 總結
- Synchronized【對象鎖】采用互斥的方式讓同一時刻至多只有一個線程能持有【對象鎖】
- 它的底層由monitor實現的,monitor是jvm級別的對象(C++實現),線程獲得鎖需要使用對象(鎖)關聯monitor
- 在monitor內部有三個屬性,分別是owner、entrylist、waitset
- 其中owner是關聯的獲得鎖的線程,并且只能關聯一個線程;entrylist關聯的是處于阻塞狀態的線程; waitset關聯的是處于Waiting狀態的線程
2 synchronized關鍵字的底層原理–進階
Monitor實現的鎖屬于
重量級鎖
,你了解過鎖升級
碼?
- Monitor實現的鎖屬于重量級鎖,里面涉及到了用戶態和內核態的切換、進程的上下文切換,成本較高,性能比較低.
- 在JDK 1.6引入了兩種新型鎖機制:偏向鎖和輕量級鎖,它們的引入是為了解決在沒有多線程競爭或基本沒有競爭的場景下因使用傳統鎖機制帶來的性能開銷問題。
1.1 Monitor重量級鎖
lock對象鎖是怎么關聯上Monitor的?
每個Java對象都可以關聯一個Monitor對象,如果使用synchronized給對象上鎖(重量級)之后,該對象頭的Mark Word 中就被設置指向Monitor對象的指針
通過java對象的內存結構來說明:
MarkWord
1.2 輕量級鎖
在很多的情況下,在Java程序運行時,同步塊中的代碼都是不存在競爭的,不同的線程交替的執行同步塊中的代碼。這種情況下,用重量級鎖是沒必要的。因此JVM引入了輕量級鎖的概念。
1.3 偏向鎖
輕量級鎖在沒有競爭時(就自己這個線程),每次重入仍然需要執行CAS操作。Java 6中引入了偏向鎖來做進一步優化:只有第一次使用CAS將線程ID設置到對象的Mark Word頭,之后發現這個線程ID是自己的就表示沒有競爭,不用重新CAS。以以后只要不發生競爭,這個對象就歸該線程所有
1.4 總結
Java中的synchronized有偏向鎖、輕量級鎖、重量級鎖三種形式,分別對應了鎖只被一個線程持有、不同線程交替持有鎖、多線程競爭鎖三種情況。
一旦鎖發生了競爭都會升級為重量級鎖
3 JMM(java memory Model–Java內存模型)
JMM(Java Memory Model)Java內存模型,定義了共享內
中多線程程序讀寫操作的行為規范
,通過這些規則來規范對內存的讀寫操作從而保證指令的正確性
- JMM(Java Memory Model)Java內存模型,定義了共享內存中多線程程序讀寫操作的行為規范,通過這些規則來規范對內存的讀寫操作從而保證指令的正確性
- JMM把內存分為兩塊,一塊是私有線程的工作區域(工作內存),一塊是所有線程的共享區域(主內存)
- 線程跟線程之間是相互隔離,線程跟線程交互需要通過主內存
4 CAS
4.1 基本概念
比對失敗,重新讀一份V到線程B的A中,設置閾值,多少次之后還沒修改成功,就放棄修改。
4.2 底層實現
CAS 底層依賴于一個Unsafe類來直接調用操作系統底層的cAs指令
native修飾的本地方法,是系統提供的,不是java提供的,是由C或者C++實現的。
4.3 樂觀鎖和悲觀鎖
CAS是基于樂觀鎖的思想:最樂觀的估計,不怕別的線程來修改共享變量,就算改了也沒關系,我吃虧點再重試唄。
synchronized是基于悲觀鎖的思想:最悲觀的估計,得防著其它線程來修改共享變量,我上了鎖你們都別想改,我改完了解開鎖,你們才有機會。
4.4 總結
4 volatile
請談談對volatile的理解?
volatile是一個關鍵字,一旦一個共享變量(類的成員變量、類的靜態成員變量)被volatile修飾之后,那么就具備了兩層語義:
①保證線程間的可見性
②禁止進行指令重排序
4.1 保證線程間的可見性
4.2 volatile禁止指令重排序
測試:
a.引入依賴
b.加注解
c.沒有main方法無法執行,自己指定
d.查看執行結果
在y
上加volatile關鍵字運行結果:在X上不行。
4.3 總結
5 AQS
5.1 基本概念
5.2 基本工作機制
多個線程爭搶資源如何保證原子性?
AQS是公平鎖還是非公平鎖?
既可以實現公平鎖也可以實現非公平鎖。
線程1釋放鎖之后,新來的線程5和等待隊列的隊首線程競爭鎖
新的線程與隊列中的線程共同來搶資源,是非公平鎖
新的線程到隊列中等待,只讓隊列中的head線程獲取鎖,是公平鎖
5.3 總結
5 ReentrantLock實現原理
ReentrantLock翻譯過來是可重入鎖,相對于synchronized它具備以下特點:
- 可中斷:
- 可以設置超時時間:超過時間沒有獲取到鎖可以放棄獲取鎖
- 可以設置公平鎖
- 支持多個條件變量:可以設置多個條件讓線程進入等待狀態
- 與synchronized一樣,都支持重入
總結:
7 synchronized與Lock有什么區別?
8 死鎖產生的條件
8.1 基本概念
8.2 死鎖診斷
9 ConcurrentHashMap
10 導致并發程序出現問題的根本原因
導致并發程序出現問題的根本原因
(或者說:java程序中怎么保證多線程的執行安全)
java并發編程的三大特性:
- 原子性
- 可見性
- 有序性
三、線程池
1 線程池的核心參數
說一下線程池的核心參數
(線程池的執行原理知道嗎)
1.1 核心參數
1.2 執行原理
2 線程池中常見阻塞隊列
3 如何確定核心線程數
4 線程池種類
5 為什么不建議Executor創建線程池
四、使用場景
1 線程池使用場景
線程池使用場景(CountDownLatch、future)
你做過的項目中哪里用到了多線程
2 es數據批量導入
3 數據匯總
4 異步調用
保存搜索記錄,不能影響當前搜索的搜索結果。
5 如何控制某個方法允許并發訪問的數量
6 談談對TreadLocal的理解
6.1 set方法
6.2 get方法/remove方法
6.3 ThreadLoca內存泄漏問題
java對象中四種引用類型:強引用、軟引用、弱引用、虛引用