10 ConcurrentHashMap
ConcurrentHashMap的鎖分段技術(數據分段存儲,分段枷鎖)
首先將數據分成一段一段的存儲,然后給每一段數據配一把鎖,當一個線程占用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問。
數據結構:

ConcurrentHashMap為了提高本身的并發能力,在內部采用了一個叫做Segment的結構,一個Segment其實就是一個類Hash Table的結構,Segment內部維護了一個鏈表數組。當對HashEntry數組的數據進行修改時,必須首先獲得它對應的Segment鎖。
高并發:ConcurrentHashMap定位一個元素的過程需要進行兩次Hash操作,第一次Hash定位到Segment,第二次Hash定位到元素所在的鏈表的頭部。在最理想的情況下,ConcurrentHashMap可以最高同時支持Segment數量大小的寫操作(剛好這些寫操作都非常平均地分布在所有的Segment上),所以,通過這一種結構,ConcurrentHashMap的并發能力可以大大的提高。總的Map包含了16個Segment(默認數量),每個Segment內部包含16個HashEntry(默認數量),這樣對于這個key所在的Segment加鎖的同時,其他15個Segmeng還能正常使用,在性能上有了大大的提升。
詳細解釋一下Segment里面的成員變量的意義:
count:Segment中元素的數量
modCount:對table的大小造成影響的操作的數量(比如put或者remove操作)
threshold:閾值,Segment里面元素的數量超過這個值依舊就會對Segment進行擴容
table:鏈表數組,數組中的每一個元素代表了一個鏈表的頭部
loadFactor:負載因子,用于確定threshold
volatile的保證:對volatile域的寫入操作happens-before于每一個后續對同一個域的讀寫操作。所以,每次判斷count變量的時候,即使恰好其他線程改變了segment也會體現出來。
get方法沒有使用鎖來同步,只是判斷獲取的entry的value是否為null,為null時才使用加鎖的方式再次去獲取。
put 操作:首先對Segment的put操作是加鎖完成的。因為每個HashEntry中的next也是final的,沒法對鏈表最后一個元素增加一個后續entry所以新增一個entry的實現方式只能通過頭結點來插入了。
remove 操作:先定位Segment的過程,然后確定需要刪除的元素的位置, 程序就將待刪除元素前面的那一些元
素全部復制一遍,然后再一個一個重新接到鏈表上去,
多線程知識點問答
1創建線程的幾種方式? Wait,sleep分別是誰的方法,區別?線程間的通信方式?
繼承Thread類創建線程,實現Runnable接口創建線程,使用Callable和Future創建線程 ,使用線程池例如用Executor框架。 Wait是在Object.java中的方法,sleep是在Thread.java中的方法。 區別是wait是當前運行線程進入阻塞狀態并釋放鎖,sleep使線程進入阻塞狀態但不釋放鎖。 使用wait和notify實現線程間的通信。
2 介紹下什么是死鎖,遇見過死鎖么?你是怎么排查的?(可以通過JPS排查)
死鎖是指兩個或兩個以上的進程在執行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去,通過jps排查。
3 創建線程池的幾種方式,線程池有什么好處?
單線程的線程池 固定大小的線程池 一個可緩存的線程池 一個大小無限的線程池
頻繁創建線程就會大大降低系統的效率,因為頻繁創建線程和銷毀線程需要時間。線程池可以使得線程可以復用,就是執行完一個任務,并不被銷毀,而是可以繼續執行其他的任務。
4 線程繼承和接口的區別,接口有什么好處?
java中我們想要實現多線程常用的有兩種方法,繼承Thread 類和實現Runnable 接口,有經驗的程序員都會
選擇實現Runnable接口 ,其主要原因有以下兩點:首先,java只能單繼承,因此如果是采用繼承Thread的方法,那么在以后進行代碼重構的時候可能會遇到問題,因為你無法繼承別的類了。其次,如果一個類繼承Thread,則不適合資源共享。但是如果實現了Runable接口的話,則很容易的實現資源共享。
5 synchronized ,lock ,reentrantlock 的區別,用法及原理
ReenTrantLock可以指定是公平鎖還是非公平鎖。而synchronized只能是非公平鎖;ReenTrantLock提供了一個Condition(條件)類,可實現分組喚醒線程,而不是像synchronized要么隨機喚醒一個線程要么喚醒全部線程;ReenTrantLock提供了一種能夠中斷等待鎖的線程的機制,通過lock.lockInterruptibly()來實現這個機制。synchronized不可中斷
6 countDownLatch 與 CyclicBarrier 的用法
CountDownLatch的計數器,線程完成一個記錄一個,計數器是遞減 計數器,只能使用一次;CyclicBarrier的計數器 更像是一個閥門,需要所有線程都到達,閥門才能打開,然后繼續執行,計數器是遞增 計數器提供reset功能,可以多次使用
7 ThreadLocal 的用法及原理
ThreadLocal用處就是用來把實例變量共享成全局變量,在程序的任何方法中都可以訪問到該實例變量;
線程隔離的原理, ThreadLocalMap類是ThreadLocal類的一個靜態內部類,它實現了鍵值對的設置和獲取,每個線程中都有一個獨立的ThreadLocalMap副本(key-ThreadLocal,value-副本),它所存儲的值(副本),只能被當前線程讀取和修改。ThreadLocal類通過操作每一個線程特有的ThreadLocalMap副本,從而實現了專屬變量訪問在不同線程中的隔離。因為每個線程的變量都是自己特有的,完全不會有并發錯誤。
8 volatile 關鍵字的作用及用法
保證內存可見性 , volatile關鍵字用于聲明簡單類型變量,如int、float、 boolean等數據類型。如果這些簡單數據類型聲明為volatile,對它們的操作就會變成原子級別的。
9 樂觀鎖和悲觀鎖
10 對公平鎖,非公平鎖,可重入鎖,自旋鎖,讀寫鎖的理解
鎖是基于線程的分配,可重入鎖: 可以被單個線程多次獲取。可中斷鎖: 某一線程A正在執行鎖中的代碼,另一線程B正在等待獲取該鎖,可能由于等待時間過長,線程B不想等待了,想先處理其他事情,我們可以讓它中斷自己或者在別的線程中中斷它,種就是可中斷鎖。 公平鎖:即盡量以請求鎖的順序來獲取鎖。自旋鎖,一個線程A在獲得普通鎖后,如果再有線程B試圖獲取鎖,那么這個線程B將會(阻塞)。讀寫所:讀寫鎖將對一個資源(比如文件)的訪問分成了2個鎖,一個讀鎖和一個寫鎖,正因為有了讀寫鎖,才使得多個線程之間的讀操作不會發生沖突。
11 CAS是什么及底層原理
CAS是一條CPU并發原語。判斷內存某個位置的值是否為預期值,如果是更改為新值,這個過程是原子的。
底層原理:Unsafe類是CAS的核心類,由于java方法無法直接訪問底層系統,需要通過本地(native)方法來訪問,基于該類可以直接操作特定內存的數據。Unsafe類中的所有方法都是native修飾的,也就是說Unsafe類中的方法都直接調用操作系統底層資源執行相應任務
12 ArrayBlockingQueue,LinkedBlockQueue,SynchronousQueue等等堵塞隊列的理解
SynchronousQueue是無界的,是一種無緩沖的等待隊列,但是由于該Queue本身的特性,在某次添加元素后必須等待其他線程取走后才能繼續添加;可以認為SynchronousQueue是一個緩存值為1的阻塞隊列
LinkedBlockingQueue是無界的,是一個無界緩存的等待隊列。當隊列緩沖區達到最大值緩存容量時,才會阻塞生產者隊列,直到消費者從隊列中消費掉一份數據,生產者線程會被喚醒,反之對于消費者這端的處理也基于同樣的原理。
在高并發的情況下生產者和消費者可以并行地操作隊列中的數據,以此來提高整個隊列的并發性能。
ArrayBlockingQueue是有界的,是一個有界緩存的等待隊列。在生產者放入數據和消費者獲取數據,都是共用同一個鎖對象,由此也意味著兩者無法真正并行運行,這點尤其不同于LinkedBlockingQueue;ArrayBlockingQueue和LinkedBlockingQueue是兩個最普通、最常用的阻塞隊列,一般情況下,處理多線程間的生產者消費者問題,使用這兩個類足以。
13 ThreadPoolExecutor 的傳入參數及內部工作原理
14 給你一個具體的業務場景,讓你使用ThreadPoolExecutor 創建一個合適的線程池
15 分布式環境下怎么保證線程安全。

浙公網安備 33010602011771號