百度怎么做自己的網(wǎng)站怎么在百度上發(fā)帖推廣
文章導(dǎo)讀
- 引言
- 考點(diǎn)1. CAS 指令(重點(diǎn))
- 一、什么是CAS
- 二、CAS 的優(yōu)點(diǎn)
- 三、CAS 的缺點(diǎn)
- 四、ABA問(wèn)題
- 五、相關(guān)面試題
- 考點(diǎn)2. 信號(hào)量(semaphore)
- 一、基本概念
- 二、信號(hào)量的主要操作
- 三、信號(hào)量的應(yīng)用
- 四、相關(guān)面試題
- 考點(diǎn)3、CountDownLatch 類
- 一、主要用途
- 二、主要方法
- 三、示例
- 考點(diǎn)4、Callable 接口
- Callable 與 Runnable 的主要區(qū)別
- 使用場(chǎng)景
- 示例
- 相關(guān)面試題
- 考點(diǎn)5、多線程下的數(shù)據(jù)結(jié)構(gòu)
- 一、多線程環(huán)境使用ArrayList
- 二、多線程環(huán)境下使用哈希表
- 1、Hashtable
- 2、ConcurrentHashMap(重點(diǎn))
- 相關(guān)面試題
- 考點(diǎn)五、其他常見(jiàn)面試題
引言
本篇文章總結(jié)了多線程中面試頻率比較高的考點(diǎn),內(nèi)容可能比較瑣碎,但是如果能夠堅(jiān)持看完,注意總結(jié)積累,相信對(duì)面試會(huì)有很大幫助。多線程內(nèi)容較多,用一篇文章寫(xiě)完可能篇幅過(guò)長(zhǎng),我打算用兩篇文章來(lái)總結(jié),本篇主要寫(xiě)的是多線程中輔助加鎖的數(shù)據(jù)結(jié)構(gòu)和指令,下一篇主要講的是鎖策略。
考點(diǎn)1. CAS 指令(重點(diǎn))
一、什么是CAS
CAS(Compare-and-Swap)是一種用于實(shí)現(xiàn)多線程同步的原子指令。它涉及到三個(gè)操作數(shù):內(nèi)存位置(V)、預(yù)期原值(A)和新值(B)。如果內(nèi)存位置的值與預(yù)期原值相匹配,那么處理器會(huì)自動(dòng)將該位置值更新為新值,這個(gè)操作是原子的。
CAS 操作包含三個(gè)關(guān)鍵動(dòng)作:
- 比較(Compare):將內(nèi)存位置的值與預(yù)期原值進(jìn)行比較。
- 交換(Swap):如果比較相等,那么處理器會(huì)自動(dòng)將該內(nèi)存位置的值更新為新值。
- 原子性(Atomicity):上述整個(gè)比較和交換的操作是原子的,即該操作在執(zhí)行過(guò)程中不會(huì)被其他線程的操作打斷。
CAS 指令一般是基于硬件實(shí)現(xiàn)的,在 Intel 處理器中,它可以通過(guò) LOCK 前綴的 CMPXCHG 指令來(lái)實(shí)現(xiàn)。在 Java 中,CAS 操作被廣泛用于實(shí)現(xiàn)非阻塞算法,如原子變量類(java.util.concurrent.atomic
包下的類)中的 getAndIncrement
、compareAndSet
等方法都是基于 CAS 實(shí)現(xiàn)的。
二、CAS 的優(yōu)點(diǎn)
- 非阻塞算法:CAS 允許線程在不進(jìn)入阻塞狀態(tài)的情況下進(jìn)行并發(fā)操作,這有助于減少線程切換的開(kāi)銷,提高系統(tǒng)的并發(fā)性能。
- 無(wú)需使用鎖:在多個(gè)線程競(jìng)爭(zhēng)同一個(gè)資源時(shí),傳統(tǒng)的鎖機(jī)制可能會(huì)導(dǎo)致線程阻塞,而 CAS 可以在不依賴鎖的情況下實(shí)現(xiàn)線程間的同步。
三、CAS 的缺點(diǎn)
- 循環(huán)時(shí)間長(zhǎng)開(kāi)銷大:如果 CAS 操作一直不成功,那么線程會(huì)一直處于自旋狀態(tài)(返回失敗并持續(xù)重試),這會(huì)增加 CPU 的負(fù)擔(dān)。
- 只能保證一個(gè)共享變量的原子操作:當(dāng)需要對(duì)多個(gè)共享變量進(jìn)行操作時(shí),CAS 就無(wú)法保證操作的原子性了。這種情況下,需要使用鎖或其他同步機(jī)制來(lái)保證操作的原子性。
四、ABA問(wèn)題
-
情景:
- 假設(shè)小明有存款1000元,他去ATM機(jī)上取100元,服務(wù)器產(chǎn)生了兩個(gè)線程處理。線程1執(zhí)行完存款變?yōu)?00,線程2CAS指令比較失敗,無(wú)法執(zhí)行。
- 就在小明取錢的時(shí)候,小明的爸爸給小明的銀行賬戶轉(zhuǎn)了100元,產(chǎn)生了線程3。如果線程3是在線程1執(zhí)行后才產(chǎn)生的,那么就會(huì)出現(xiàn)存款從 1000 -> 900 -> 1000 的過(guò)程,于是再執(zhí)行線程2的CAS指令就會(huì)成功。本來(lái)小明只想取100元,現(xiàn)在取錢操作執(zhí)行了兩次,取出了200元!
- 假設(shè)小明有存款1000元,他去ATM機(jī)上取100元,服務(wù)器產(chǎn)生了兩個(gè)線程處理。線程1執(zhí)行完存款變?yōu)?00,線程2CAS指令比較失敗,無(wú)法執(zhí)行。
-
定義:
在CAS操作中,線程會(huì)首先讀取某個(gè)內(nèi)存位置的值(我們稱之為預(yù)期值A(chǔ)),然后執(zhí)行CAS操作,嘗試將該內(nèi)存位置的值修改為新的值(我們稱之為B),但前提是內(nèi)存位置的值必須仍然是預(yù)期值A(chǔ)。如果在讀取值和嘗試修改值之間,有其他線程修改了該內(nèi)存位置的值(比如從A改為了B,然后又改回了A),那么CAS操作會(huì)錯(cuò)誤地認(rèn)為該值沒(méi)有變化,從而成功執(zhí)行,這就會(huì)導(dǎo)致ABA問(wèn)題。
在以上情景中,存款從1000變900再變成1000的過(guò)程所導(dǎo)致的取錢兩次的BUG就是ABA問(wèn)題。
-
解決方法:
給要修改的值, 引?版本號(hào). 在 CAS ?較數(shù)據(jù)當(dāng)前值和舊值的同時(shí), 也要?較版本號(hào)是否符合預(yù)期。例如:
給存款引入版本號(hào),每次執(zhí)行線程時(shí)版本號(hào)加1.
版本號(hào)為1,線程1執(zhí)行扣款成功,存款為900,版本號(hào)+1變?yōu)?,線程3執(zhí)行存入成功,存款為1000,版本號(hào)+1變?yōu)?,線程2執(zhí)行,版本號(hào)與之前讀取的不同,執(zhí)行失敗。
五、相關(guān)面試題
- 講解下你??理解的 CAS 機(jī)制。
- ABA問(wèn)題怎么解決?
忠告:相關(guān)面試題的答案我不會(huì)給出,讀者應(yīng)自己總結(jié)積累,盲目背誦答案已經(jīng)過(guò)時(shí),面試場(chǎng)上的八股文已被千變?nèi)f化的情景題目所取代,只有自己總結(jié)積累經(jīng)驗(yàn)和知識(shí)才能應(yīng)對(duì)變化,才能讓面試官青睞!
考點(diǎn)2. 信號(hào)量(semaphore)
一、基本概念
- 定義:信號(hào)量是一個(gè)非負(fù)整數(shù),用于表示某種資源的數(shù)量。它有兩個(gè)主要操作:P(等待)和V(釋放)。
- 作用:實(shí)現(xiàn)任務(wù)之間的同步或臨界資源的互斥訪問(wèn),常用于協(xié)助一組相互競(jìng)爭(zhēng)的任務(wù)來(lái)訪問(wèn)臨界資源。
二、信號(hào)量的主要操作
-
P(等待)操作:
- 當(dāng)一個(gè)進(jìn)程或線程需要訪問(wèn)共享資源時(shí),它會(huì)嘗試執(zhí)行P操作。
- 如果信號(hào)量的值大于0,表示資源可用,進(jìn)程或線程可以繼續(xù)訪問(wèn)資源,并將信號(hào)量的值減1。
- 如果信號(hào)量的值等于0,表示資源已被占用,進(jìn)程或線程會(huì)被阻塞,直到信號(hào)量的值變?yōu)檎龜?shù)。
-
V(釋放)操作:
- 當(dāng)一個(gè)進(jìn)程或線程完成對(duì)共享資源的訪問(wèn)時(shí),它會(huì)執(zhí)行V操作,將信號(hào)量的值加1。
- 如果有其他等待進(jìn)程被阻塞,它們中的一個(gè)將被喚醒并獲得對(duì)資源的訪問(wèn)權(quán)限。
代碼示例:
public static void main(String[] args) {// 信號(hào)量為4,表明有四個(gè)資源待訪問(wèn)Semaphore semaphore = new Semaphore(4);// 寫(xiě)一個(gè)線程訪問(wèn)訪問(wèn)資源Thread t = new Thread(() -> {try {// accquire方法表示P操作semaphore.acquire();// do something ...Thread.sleep(1000);// release方法表示V操作semaphore.release();} catch(InterruptedException e){e.printStackTrace();}});t.start();}
三、信號(hào)量的應(yīng)用
信號(hào)量在操作系統(tǒng)和并發(fā)編程中有著廣泛的應(yīng)用,包括但不限于:
- 進(jìn)程同步:控制多個(gè)進(jìn)程的執(zhí)行順序,保證數(shù)據(jù)的正確處理。
- 臨界資源的互斥訪問(wèn):保護(hù)共享資源,防止數(shù)據(jù)競(jìng)爭(zhēng)和沖突。
- 生產(chǎn)者-消費(fèi)者問(wèn)題:在生產(chǎn)者-消費(fèi)者模型中,通過(guò)信號(hào)量來(lái)控制資源的生產(chǎn)和消費(fèi)。
- 線程池管理:控制線程池中的線程數(shù)量,以控制系統(tǒng)的負(fù)載。
- 順序控制:確保多個(gè)任務(wù)按照特定的順序執(zhí)行。
綜上所述,信號(hào)量是一種重要的同步機(jī)制,通過(guò)合理地控制信號(hào)量的值,可以實(shí)現(xiàn)對(duì)共享資源的互斥訪問(wèn)和同步操作,從而避免并發(fā)編程中的常見(jiàn)問(wèn)題。
四、相關(guān)面試題
- 簡(jiǎn)單解釋一下什么是信號(hào)量?
- 什么場(chǎng)景下會(huì)使用到信號(hào)量?
考點(diǎn)3、CountDownLatch 類
CountDownLatch
是 Java 并發(fā)包 java.util.concurrent
中的一個(gè)非常有用的同步輔助類,它允許一個(gè)或多個(gè)線程等待一組其他線程完成操作。這個(gè)類通過(guò)讓一個(gè)或多個(gè)線程等待其他線程完成一組操作來(lái)協(xié)調(diào)線程。CountDownLatch
初始化時(shí)設(shè)置一個(gè)計(jì)數(shù)器(count),這個(gè)計(jì)數(shù)器代表等待完成的操作的數(shù)量。
一、主要用途
- 等待多個(gè)線程完成:
CountDownLatch
允許一個(gè)或多個(gè)線程等待其他一組線程完成它們的任務(wù)。例如,在啟動(dòng)多個(gè)線程進(jìn)行并行計(jì)算時(shí),你可能希望等待所有線程都完成計(jì)算后再繼續(xù)執(zhí)行后續(xù)操作。 - 性能優(yōu)化:通過(guò)并行處理,可以提高應(yīng)用程序的響應(yīng)速度和吞吐量。
CountDownLatch
可以幫助在并行處理完成后同步后續(xù)操作。
二、主要方法
CountDownLatch(int count)
:構(gòu)造函數(shù),初始化計(jì)數(shù)器值為給定的count
值。void await()
:使當(dāng)前線程在鎖存器倒計(jì)數(shù)至零之前一直處于等待狀態(tài),除非線程被中斷。void await(long timeout, TimeUnit unit)
:使當(dāng)前線程在鎖存器倒計(jì)數(shù)至零之前一直處于等待狀態(tài),或者從當(dāng)前時(shí)間起已經(jīng)過(guò)了指定的等待時(shí)間,或者線程被中斷。void countDown()
:遞減鎖存器的計(jì)數(shù),如果計(jì)數(shù)到達(dá)零,則釋放所有等待的線程。
三、示例
下面是一個(gè)簡(jiǎn)單的示例,展示了如何使用 CountDownLatch
來(lái)等待一組線程完成它們的任務(wù)。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class CountDownLatchExample {public static void main(String[] args) throws InterruptedException {int taskCount = 5;ExecutorService executor = Executors.newFixedThreadPool(taskCount);CountDownLatch latch = new CountDownLatch(taskCount);for (int i = 0; i < taskCount; i++) {executor.submit(() -> {try {// 模擬任務(wù)執(zhí)行Thread.sleep(1000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}latch.countDown(); // 完成任務(wù),減少計(jì)數(shù)器});}// 等待所有任務(wù)完成latch.await();System.out.println("所有任務(wù)完成");executor.shutdown();}
}
在這個(gè)示例中,我們創(chuàng)建了一個(gè)包含 5 個(gè)任務(wù)的線程池。每個(gè)任務(wù)完成后,都會(huì)調(diào)用 countDown()
方法來(lái)減少 CountDownLatch
的計(jì)數(shù)器。主線程通過(guò)調(diào)用 await()
方法等待,直到計(jì)數(shù)器的值達(dá)到 0,即所有任務(wù)都已完成。然后,主線程繼續(xù)執(zhí)行并打印出 “所有任務(wù)完成”。
考點(diǎn)4、Callable 接口
在Java中,Callable
接口是Java并發(fā)API的一部分,它位于 java.util.concurrent
包中。與 Runnable
接口不同,Callable
接口可以返回一個(gè)結(jié)果,并且可能拋出一個(gè)異常。這使得 Callable
接口非常適合用于那些需要返回值的并發(fā)任務(wù)。
Callable
接口的定義如下:
@FunctionalInterface
public interface Callable<V> {/*** Computes a result, or throws an exception if unable to do so.** @return computed result* @throws Exception if unable to compute a result*/V call() throws Exception;
}
Callable 與 Runnable 的主要區(qū)別
- 返回值:
Runnable
接口的run
方法沒(méi)有返回值,而Callable
接口的call
方法可以返回一個(gè)泛型類型的值。 - 異常處理:
Runnable
的run
方法不允許拋出受檢查的異常(checked exceptions),而Callable
的call
方法可以。如果call
方法拋出了一個(gè)異常,這個(gè)異常將被封裝在一個(gè)ExecutionException
中,這個(gè)異常是由Future.get()
方法拋出的。
使用場(chǎng)景
當(dāng)你需要執(zhí)行一個(gè)任務(wù),并且這個(gè)任務(wù)完成后需要返回一個(gè)結(jié)果時(shí),就可以使用 Callable
。例如,你可能需要從遠(yuǎn)程服務(wù)器獲取數(shù)據(jù),或者執(zhí)行一些計(jì)算并返回結(jié)果。
示例
創(chuàng)建線程計(jì)算 1 + 2 + 3 + … + 1000,
使用 Run 版本:
-
創(chuàng)建?個(gè)類 Result,包含?個(gè) sum 表示最終結(jié)果, lock 表?線程同步使?的鎖對(duì)象。
-
main ?法中先創(chuàng)建 Result 實(shí)例, 然后創(chuàng)建?個(gè)線程 t. 在線程內(nèi)部計(jì)算 1 + 2 + 3 + … + 1000.
-
主線程同時(shí)使? wait 等待線程 t 計(jì)算結(jié)束. (注意, 如果執(zhí)行到 wait 之前, 線程 t 已經(jīng)計(jì)算完了, 就不必等待了)。
-
當(dāng)線程 t 計(jì)算完畢后, 通過(guò) notify 喚醒主線程, 主線程再打印結(jié)果.
public class Demo18 {static class Result {public int sum = 0;public Object lock = new Object();}public static void main(String[] args) throws InterruptedException {Result result = new Result();Thread t = new Thread() {@Overridepublic void run() {int sum = 0;for (int i = 1; i <= 1000; i++) {sum += i;}synchronized (result.lock) {result.sum = sum;result.lock.notify();}}};t.start();synchronized (result.lock) {while (result.sum == 0) {result.lock.wait();}System.out.println(result.sum);}}
}
可以看到,上述代碼需要?個(gè)輔助類 Result,還需要使??系列的加鎖和 wait notify 操作,代碼復(fù)雜,容易出錯(cuò)。
使用Callable版本:
- 創(chuàng)建?個(gè)匿名內(nèi)部類,實(shí)現(xiàn) Callable 接?。 Callable 帶有泛型參數(shù),泛型參數(shù)表?返回值的類型。
- 重寫(xiě) Callable 的 call ?法,完成累加的過(guò)程,直接通過(guò)返回值返回計(jì)算結(jié)果.
- 把 callable 實(shí)例使? FutureTask 包裝?下。
- 線程的構(gòu)造?法傳? FutureTask。 此時(shí)新線程就會(huì)執(zhí)行 FutureTask 內(nèi)部的 Callable 的 call 方法完成計(jì)算,計(jì)算結(jié)果就放到了 FutureTask 對(duì)象中。
- 在主線程中調(diào)? futureTask.get() 能夠阻塞等待新線程計(jì)算完畢. 并獲取到 FutureTask 中的結(jié)果。
public class Demo18 {public static void main(String[] args) throws InterruptedException, ExecutionException {Callable<Integer> callable = new Callable<Integer>() {@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 1; i <= 1000; i++) {sum += i;}return sum;}};FutureTask<Integer> futureTask = new FutureTask<>(callable);Thread t = new Thread(futureTask);t.start();int result = futureTask.get();System.out.println(result);}
}
可以看到,使? Callable 和 FutureTask 之后,代碼簡(jiǎn)化了很多,也不必?動(dòng)寫(xiě)線程同步代碼了。
相關(guān)面試題
請(qǐng)你說(shuō)說(shuō)Callable 和 Runnable 的主要區(qū)別?
考點(diǎn)5、多線程下的數(shù)據(jù)結(jié)構(gòu)
一、多線程環(huán)境使用ArrayList
- 普通的 ArrayList 線程并不安全,在使用時(shí)必須在可能發(fā)生沖突的地方加鎖,操作復(fù)雜且容易發(fā)生死鎖。
- (使用較多)使用
Collections.synchronizedList(new ArrayList)
,synchronizedList 是標(biāo)準(zhǔn)庫(kù)提供的?個(gè)基于 synchronized 進(jìn)行線程同步的方法,它是 Collections 類的一個(gè)靜態(tài)方法,它的返回值是一個(gè)對(duì)關(guān)鍵方法加了鎖的鏈表(List)。這樣可以簡(jiǎn)化程序猿對(duì)代碼的加鎖操作,降低死鎖的風(fēng)險(xiǎn)。 - (???/strong>)使?
CopyOnWriteArrayList
,這是一個(gè)寫(xiě)時(shí)復(fù)制的容器。-
原理:
- 當(dāng)我們往?個(gè)容器添加元素的時(shí)候,不直接往當(dāng)前容器添加,而是先將當(dāng)前容器進(jìn)行Copy,復(fù)制出?個(gè)新的容器,然后新的容器里添加元素;
- 添加完元素之后,再將原容器的引用指向新的容器。
-
優(yōu)點(diǎn):
線程可以對(duì)CopyOnWrite原容器進(jìn)行并發(fā)的讀,而不需要加鎖,因?yàn)樵萜鞑粫?huì)添加任何元素,讀和寫(xiě)是不同的容器。在讀多寫(xiě)少的場(chǎng)景下,性能很高,不需要加鎖競(jìng)爭(zhēng)。
-
缺點(diǎn):
占用內(nèi)存較多且新寫(xiě)的數(shù)據(jù)不能被第?時(shí)間讀取到。
-
二、多線程環(huán)境下使用哈希表
HashMap 本身是線程不安全的,Java 又在 HashMap 的基礎(chǔ)上封裝了兩個(gè)類:
- Hashtable
- ConcurrentHashMap
1、Hashtable
Hashtable 只是在 HashMap 的基礎(chǔ)上,把關(guān)鍵方法加上了鎖(synchronized),如:
public synchronized V get(Object key)
public synchronized V put(K key, V value)
這無(wú)疑是給所有讀寫(xiě)操作都加上了鎖,線程想要訪問(wèn)同一個(gè) Hashtable 的任何數(shù)據(jù)都會(huì)直接造成鎖競(jìng)爭(zhēng)(一把鎖鎖上整個(gè)Hash表,如圖,一個(gè)每次只能有一個(gè)線程訪問(wèn)該表),一旦觸發(fā)擴(kuò)容,就只有在單個(gè)線程(觸發(fā)擴(kuò)容的線程)上進(jìn)行,涉及大量的數(shù)據(jù)拷貝,效率非常低。
2、ConcurrentHashMap(重點(diǎn))
ConcurrentHashMap
是 Java 并發(fā)包 java.util.concurrent
中的一個(gè)非常重要的類,用于在并發(fā)環(huán)境下替代傳統(tǒng)的 HashMap
。它提供了比 Hashtable
更高的并發(fā)級(jí)別,因?yàn)?Hashtable
是同步的,這意味著在每一次訪問(wèn)時(shí),整個(gè)表都需要被鎖定,這大大降低了并發(fā)性能。ConcurrentHashMap
通過(guò)以下幾個(gè)方面的優(yōu)化和改進(jìn)來(lái)提升并發(fā)性能:
-
分段鎖(Segmentation Locking)(在 Java 8 之前):
- 在 Java 8 之前,
ConcurrentHashMap
使用分段鎖的機(jī)制來(lái)減少鎖的競(jìng)爭(zhēng)。它將整個(gè)哈希表分為多個(gè)段(Segment),每個(gè)段都維護(hù)著自己的鎖。這樣,在并發(fā)環(huán)境中,只要多個(gè)線程訪問(wèn)的是不同的段,它們就可以并行地執(zhí)行,從而減少了鎖的爭(zhēng)用。每個(gè)段內(nèi)部都維護(hù)了一個(gè)哈希表,用于存儲(chǔ)鍵值對(duì)。 - 當(dāng)需要對(duì)某個(gè)鍵進(jìn)行操作時(shí),首先通過(guò)哈希碼確定該鍵屬于哪個(gè)段,然后只鎖定該段進(jìn)行操作,而不是鎖定整個(gè)表。
- 在 Java 8 之前,
-
鎖粒度細(xì)化(Fine-grained Locking)(在 Java 8 及以后):
- 從 Java 8 開(kāi)始,
ConcurrentHashMap
放棄了分段鎖的設(shè)計(jì),轉(zhuǎn)而采用了一種更為靈活的鎖策略,即使用Node
數(shù)組加上鏈表或紅黑樹(shù)(在鏈表過(guò)長(zhǎng)時(shí))的方式來(lái)存儲(chǔ)鍵值對(duì),并通過(guò)synchronized
關(guān)鍵字或CAS
(Compare-And-Swap)操作來(lái)確保線程安全。 - 在 Java 8 的實(shí)現(xiàn)中,鎖被細(xì)化到了每個(gè)桶(bucket)上,即每個(gè)數(shù)組元素。當(dāng)多個(gè)線程訪問(wèn)不同的桶時(shí),它們可以并行地執(zhí)行。這進(jìn)一步減少了鎖的競(jìng)爭(zhēng),提高了并發(fā)性能。
- 讀操作沒(méi)有加鎖(但是使用了 volatile 保證從內(nèi)存讀取結(jié)果),只對(duì)寫(xiě)操作進(jìn)行加鎖。
- 從 Java 8 開(kāi)始,
-
使用 CAS(Compare-And-Swap)操作:
- CAS 是一種無(wú)鎖算法,用于實(shí)現(xiàn)線程間的同步,而不需要使用傳統(tǒng)的鎖。在
ConcurrentHashMap
的實(shí)現(xiàn)中,當(dāng)嘗試修改某個(gè)桶(或節(jié)點(diǎn))時(shí),會(huì)嘗試使用 CAS 操作來(lái)更新該桶的狀態(tài)。如果桶的狀態(tài)在此期間沒(méi)有被其他線程修改,則 CAS 操作成功,否則重試(CAS與版本號(hào)結(jié)合)。 - CAS 操作減少了鎖的使用,從而提高了性能,但也可能導(dǎo)致更高的 CPU 使用率,因?yàn)樾枰粩嘀卦囍钡匠晒橹?/strong>。
- CAS 是一種無(wú)鎖算法,用于實(shí)現(xiàn)線程間的同步,而不需要使用傳統(tǒng)的鎖。在
-
動(dòng)態(tài)擴(kuò)容:
- 定義:
ConcurrentHashMap
支持動(dòng)態(tài)擴(kuò)容,即當(dāng)哈希表中的元素?cái)?shù)量達(dá)到某個(gè)閾值時(shí),會(huì)自動(dòng)進(jìn)行擴(kuò)容操作,以避免哈希沖突和性能下降。與HashMap
類似,擴(kuò)容操作涉及到重新計(jì)算每個(gè)元素的哈希碼,并將其放置到新的哈希表中。但ConcurrentHashMap
的擴(kuò)容操作是并發(fā)安全的,可以在不阻塞讀操作的情況下進(jìn)行。 - 原理:
- 發(fā)現(xiàn)需要擴(kuò)容的線程,只需要?jiǎng)?chuàng)建?個(gè)新的數(shù)組,同時(shí)只搬幾個(gè)元素過(guò)去。
- 擴(kuò)容期間, 新老數(shù)組同時(shí)存在,后續(xù)每個(gè)來(lái)操作 ConcurrentHashMap 的線程,都會(huì)參與搬家的過(guò)程,每個(gè)操作負(fù)責(zé)搬運(yùn)一小部分元素,搬完最后?個(gè)元素再把老數(shù)組刪掉。
- 這個(gè)期間,插入的元素只往新數(shù)組里添加,查找需要同時(shí)查新數(shù)組和?數(shù)組。
- 定義:
-
紅黑樹(shù)優(yōu)化:
- 在 Java 8 及以后的版本中,當(dāng)某個(gè)桶中的鏈表長(zhǎng)度超過(guò)一定閾值時(shí)(默認(rèn)為 8),
ConcurrentHashMap
會(huì)將該鏈表轉(zhuǎn)換為紅黑樹(shù),以優(yōu)化查找性能。這是因?yàn)榧t黑樹(shù)在查找、插入和刪除操作上的時(shí)間復(fù)雜度比鏈表更低(在平均和最壞情況下都是 O(log n)),可以進(jìn)一步提高并發(fā)性能。
- 在 Java 8 及以后的版本中,當(dāng)某個(gè)桶中的鏈表長(zhǎng)度超過(guò)一定閾值時(shí)(默認(rèn)為 8),
綜上所述,ConcurrentHashMap
通過(guò)分段鎖(在 Java 8 之前)、鎖粒度細(xì)化(在 Java 8 及以后)、CAS 操作、動(dòng)態(tài)擴(kuò)容和紅黑樹(shù)優(yōu)化等多種機(jī)制來(lái)優(yōu)化和改進(jìn)并發(fā)性能,使其成為 Java 中處理并發(fā)哈希表的首選數(shù)據(jù)結(jié)構(gòu)。
相關(guān)面試題
- ConcurrentHashMap的讀是否要加鎖,為什么?
- 介紹下 ConcurrentHashMap的鎖分段技術(shù)?
- ConcurrentHashMap在jdk1.8做了哪些優(yōu)化?
- Hashtable和HashMap、ConcurrentHashMap 之間的區(qū)別?
考點(diǎn)五、其他常見(jiàn)面試題
以下的面試題的答案都在我之前的文章中,大家可以從中尋找答案,這里我就不再一一贅述。
-
談?wù)?volatile關(guān)鍵字的用法?
參考文章 線程安全
-
Java多線程是如何實(shí)現(xiàn)數(shù)據(jù)共享的?
JVM 把內(nèi)存分成了這幾個(gè)區(qū)域; ?法區(qū), 堆區(qū), 棧區(qū), 程序計(jì)數(shù)器。 其中堆區(qū)這個(gè)內(nèi)存區(qū)域是多個(gè)線程之間共享的。只要把某個(gè)數(shù)據(jù)放到堆內(nèi)存中,就可以讓多個(gè)線程都能訪問(wèn)到。
-
Java創(chuàng)建線程池的接?是什么?參數(shù) LinkedBlockingQueue 的作用是什么?
參考文章 線程池的認(rèn)識(shí)和使用
-
Java線程共有幾種狀態(tài)?狀態(tài)之間怎么切換的?
參考文章
線程安全
Thread類和線程的用法 -
Thread和Runnable的區(qū)別和聯(lián)系?
參考文章 Thread類和線程的用法