Java併發多線程-----真實大廠面試題彙總(含答案)

面試題1、說一說自己對於 synchronized 關鍵字的瞭解

從JVM層面的monitor對象瞭解synchronize的底層實現:
https://blog.csdn.net/qq_36520235/article/details/81176536

  1. 首先Synchronized關鍵字他可以保證他所修飾的方法或者代碼塊在任何時候都只能有一個線程可以執行。
  2. 他底層的監視器鎖(monitor)是依賴操作系統的Mutex Lock來實現的,因爲線程的掛起和喚醒都需要操作系統的幫助,而操作系統實現線程的切換是需要從用戶狀態轉換到內核狀態,這個時間比較長,時間成本比較高
  3. 在JDK1.6之前Synchronize是一種重量級鎖,但是在JDK1.6之後對鎖的實現引入了大量的優化,如自旋鎖、適應性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術來減少鎖操作的開銷。

補充:

首先如果有多個線程想要獲取鎖的話,其實會把每一個等待鎖的線程封裝成一個ObjectWaiter對象然後先放到等待鎖的集合中,然後如果有一個線程獲取到了monitor對象的話,會把對象頭MarkWord中的鎖信息(這個鎖信息是一個指針)指向monitor對象的起始地址。
在這裏插入圖片描述

Entry Set表示當前等待獲取鎖的集合,The Owner表示當前擁有鎖的線程,Wait Set表示擁有鎖的線程調用了wait()方法,然後釋放了monitor對象,等待被喚醒。
在這裏插入圖片描述

再一次從JVM中C++源碼層面研究synchronized的實現補充

(1)從JVM中的hotspoot的ObjectMonitor類的源碼去研究Synchronize:

  1. 下面的_WaitSet(線程的等待隊列)和_EntryList(線程的鎖池)其實就是,每個對象鎖的線程都會包裝成一個ObjectWaiter來放到上面的兩個集合中
  2. 其中的_owner這個就是指向持有ObjectMonitor對象的線程,當有多個線程同時獲取同一個對象資源的時候,線程會先進入_EntryList(也就是鎖池中等待),當其中一個線程A獲取到Monitor對象後,會把owner指向獲取到Monitor對象的線程A,然後monitor的計數器就會加1,然後線程A釋放鎖的時候會計數器減一,並且把_owner對象置空便於指向下一個線程,然後線程A被放入到_WaitSet等待隊列中,等待下一次被喚醒,整個操作結束。

Hotspot中的Monitor的C++源碼鏈接地址:https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/da3a1f729b2b/src/share/vm/runtime/objectMonitor.hpp

  // JVM/DI GetMonitorInfo() needs this
  ObjectWaiter* first_waiter()                                         { return _WaitSet; }
  ObjectWaiter* next_waiter(ObjectWaiter* o)                           { return o->_next; }
  Thread* thread_of_waiter(ObjectWaiter* o)                            { return o->_thread; }

  // initialize the monitor, exception the semaphore, all other fields
  // are simple integers or pointers
  ObjectMonitor() {
    _header       = NULL;
    _count        = 0; //monitor對象的計數器 
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL; //這個是一個等待隊列
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; //這個是相當於一個鎖池
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }

如果有多個線程,他們線程之間的狀態是如何轉換的:

對於一個synchronized修飾的方法(代碼塊)來說:

  1. 當多個線程同時訪問該方法,那麼這些線程會先被放進_EntryList隊列,此時線程處於blocking狀態
  2. 當一個線程獲取到了實例對象的監視器(monitor)鎖,那麼就可以進入running狀態,執行方法,此時,ObjectMonitor對象的_owner指向當前線程,_count加1表示當前對象鎖被一個線程獲取
  3. 當running狀態的線程調用wait()方法,那麼當前線程釋放monitor對象,進入waiting狀態,ObjectMonitor對象的_owner變爲null,_count減1,同時線程進入_WaitSet隊列,直到有線程調用notify()方法喚醒該線程,則該線程重新獲取monitor對象進入_Owner區
  4. 如果當前線程執行完畢,那麼也釋放monitor對象,進入waiting狀態,ObjectMonitor對象的_owner變爲null,_count減1

面試題2、說說自己是怎麼使用 synchronized 關鍵字

可以分爲以下幾種的情況:
4. 修飾實例方法和實例對象:相當於修飾當前實例的對象,如果進入同步代碼塊前需要獲取當前實例對象的鎖。
修飾實例方法:

  public synchronized void testSynchronized(){
        
    }

5. 修飾靜態方法:相當於修飾類對象(類對象就相當於是,只要是修飾的靜態方法的所在類的所有實例都會進行加鎖)

    public static synchronized void testSynchronized(){

    }

6. 修飾代碼塊:第一種鎖的是當前代碼塊所在的實例對象,第二種鎖的是當前代碼塊所在的類對象(也就是隻要是這個類new出來的所有的實例對象都會被鎖)
修飾代碼塊的當前this實例對象:

       synchronized (this) {

        }

修飾代碼塊的XXX類.class對象:

        synchronized (test.class) {

        }

面試題3、講一下 synchronized 的鎖升級的過程?

無論是作用到同步塊還是作用到同步方法,其底層的本質都是對一個對象的監視器(monitor)進行獲取,獲取後將鎖計數器設爲1也就是加1。相應的在執行 monitorexit 指令後,將鎖計數器設爲0,表明鎖被釋放,而且這個獲取的過程是排他的,也就是同時只能有且只有一個線程進來獲取到這個監視器,而其他沒有獲取到這個監視器(monitor)的線程都只能阻塞在同步塊和同步方法的入口處,進入Blocking狀態。

這裏如果其實作用到代碼塊和方法上的話,還是會有些不一樣的,如果是作用在方法上的話,其實使用並不是monitor對象,而是使用ACC_Synchronized標識符,該標識符表示這是一個同步方法,會來進行執行一個對應的調用

在JDK1.6之後對鎖進行了一些鎖的優化:從無鎖——>偏向鎖——>輕量級鎖——>重量級鎖的過程

在堆中的任何一個對象中,每個對象中都是由三部分組成的(對象頭、實例數據、對齊填充),而存儲關於當前對象是被那個線程持有的等等信息都是放到對象中的Mark word中的,下面是Mark word的對應的信息對照表:
在這裏插入圖片描述

  1. 首先無鎖就是剛開始時,只有一個線程想要獲取一個資源對象
  2. 然後獲取成功之後,就會把當前獲取到資源對象的線程ID放入到Mark word的中相對應的位置,來進行對這個資源對象進行標記。
  3. 隨着進一步又有多個線程來對同一個資源對象進行競爭,那麼此時的偏向鎖就會升級爲輕量級鎖,從偏向鎖到輕量級鎖的過程:

這是synchronize的鎖升級的全部流程圖:http://wx2.sinaimg.cn/large/e0e01e43gy1g1cozajzz3j22zf1e7u0x.jpg

面試題4、談談 synchronized和ReenTrantLock 的區別

  • (1):兩者都是可重入的鎖(什麼是可重入鎖:簡單的來說就是自己可以再次獲取自己的鎖。比如:如果一個線程已經獲取到了某個對象的鎖,但是此時這個鎖並沒有釋放,然後我還想繼續獲取這個對象的鎖的時候還是可以獲取到的,如果不是可重入鎖的話,就會造成死鎖,每次加鎖,計數器都會加1,釋放鎖的時候會減1)

  • (2):Synchronize是依賴JVM層面進行加鎖的,而ReenTrantLcok主要是依賴API層面來進行加鎖的

  • (3):ReenTrantLock 比 synchronized 增加了一些高級功能

  • ReenTrantLock 可以實現一種中斷等待鎖的線程的方法,通過lock.lockInterruptibly()來實現這個機制。也就是說正在等待的線程可以選擇放棄等待,改爲處理其他事情。

  • ReenTrantLock可以指定是公平鎖還是非公平鎖。而synchronized只能是非公平鎖,而且ReenTrantLock默認就是非公平的鎖,可以通過構造方法進行指定是公平鎖還是非公平鎖

  • 可實現選擇性通知(鎖可以綁定多個條件):synchronized關鍵字與wait()和notify/notifyAll()方法相結合可以實現等待/通知機制,ReentrantLock類當然也可以實現,但是需要藉助於Condition接口與newCondition() 方法。Condition是JDK1.5之後纔有的,它具有很好的靈活性,比如可以實現多路通知功能也就是在一個Lock對象中可以創建多個Condition實例(即對象監視器),線程對象可以註冊在指定的Condition中,從而可以有選擇性的進行線程通知,在調度線程上更加靈活。 在使用notify/notifyAll()方法進行通知時,被通知的線程是由 JVM 選擇的,用ReentrantLock類結合Condition實例可以實現“選擇性通知” ,這個功能非常重要,而且是Condition接口默認提供的。而synchronized關鍵字就相當於整個Lock對象中只有一個Condition實例,所有的線程都註冊在它一個身上。如果執行notifyAll()方法的話就會通知所有處於等待狀態的線程這樣會造成很大的效率問題,而Condition實例的signalAll()方法 只會喚醒註冊在該Condition實例中的所有等待線程

面試題5、volitile關鍵字的作用,原理。

  • 保證數據的可見性,也就是當使用了volitile的修飾變量的時候,只要當這個變量有改變就會從該線程的本地內存中的共享變量刷新到主內存中,保證了主內存中的數據一直都是最新的數據,如果是讀一個volitile的數據的時候,JMM會把該線程相對應的本地內存變量置爲無效,因爲該本地變量無效了,所以就會去主內存中獲取最新的數據
  • 防止指令進行重排序,因爲JVM在設計的時候爲了最大化的利用CUP的效率,他規定了在不影響正常的輸出結果的情況下,所有的指令都是亂序的,爲了防止普通的讀寫操作和volitile修飾的變量的讀寫操作的區別,在有volitile修飾的變量的讀寫的時候都會加上內存屏障來防止指令重排序

volitile的致命缺點:

  • 不支持原子性,但是synchronized支持原子性,也可以間接保證可見性

面試題6、可重入鎖的用處及實現原理

可重入鎖可以用於比如一個線程需要重複多次獲取鎖的場景。
可重入鎖的原理:

  • 其實就是基於AQS的原理的

面試題7、講講對CAS和AQS的理解

美團對unsafe類的原理:https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html

CAS的原理:

  • 首先CAS是一個採用的是樂觀鎖的思想進行無鎖算法實現的,其實就是(compare and swap)比較然後交換,比如CompareAndSwap(V,E,N)V就表示的是當前內存位置的值,E就表示的是預期的要比較的值,N就表示如果跟預期的值是一樣的話就把當前內存位置的值要更新的新的值,如果當前內存位置的值和預期的值不一樣的話,就不做任何操作,底層的實現是依賴於Unsafe類的方法
  • CAS的原理實現在Unasafe類中,主要是基於native修飾的方法,通過看底層對內存的地址是不是有變化來進行CAS操作,在atomic 包下的那些AtomicIntegerArray類中,其實也是通過Unsafe的arrayBaseOffset、arrayIndexScale分別獲取數組首元素的偏移地址base及單個元素大小因子scale(這個其實就是每個數組中的單個元素所佔用的字節大小),然後通過數組對象的起始內存地址推出來整個數組佔用的長度,來判斷這個數組的操作是不是原子性的
/**
	*  CAS
  * @param o         包含要修改field的對象
  * @param offset    對象中某field的偏移量
  * @param expected  期望值
  * @param update    更新值
  * @return          true | false
  */
public final native boolean compareAndSwapObject(Object o, long offset,  Object expected, Object update);

在這裏插入圖片描述

AQS的原理:

  • 是什麼:首先得先知道AQS是一個同步隊列的組件,用來實現各種鎖或者其他同步組件的基礎框架。
  • 用來幹嘛的:使用的方式主要是通過繼承,子類通過繼承同步器並實現他的抽象方法來進行管理同步狀態(它又支持獨佔式的獲取、和共享式的獲取同步狀態),利用AQS實現的鎖有ReentrantLock、ReentrantReadWriteLock、CountDownLatch等。
  • 原理;(待續。。。。)

面試題8、靜態變量會有線程安全問題嗎?局部變量呢

靜態變量:非線程安全的

  • 靜態變量其實就是類變量,位於方法區,被所有對象進行共享,共享一份內存,一旦靜態變量被改變,其他對象都對修改可見,所以是非線程安全的
    局部變量:線程安全的
  • 因爲局部變量都位於每個本地線程的棧貞中的工作內存中,每個線程中的變量都是獨立的,互不影響,所以不會出現線程不安全。

面試題9、線程池介紹下

(1)線程池的七個參數的意思:

  1. corePoolSize(核心線程的數量)
  2. maximumPoolSize(線程池的最大數量)
  3. keepAliveTime(線程的存活時間)
  4. timeUnit(線程的存活時間的單位)
  5. BlockingQueue (阻塞隊列的類型)
  6. ThreadFactory(生產線程的線程工廠)
  7. RejectedExecutionHandler(如果整個線程池都滿的話,需要採用 的拒絕策略)

(2)線程池的工作流程:

  1. 如果有新的任務過來,先進行判斷核心線程池的線程是不是都滿了,如果沒有滿的話就直接進行新建一個線程進行執行任務,如果核心線程池滿的話,就進入下一步
  2. 此時會先進行判斷我的阻塞隊列是不是滿了(這裏選擇的阻塞對列十分重要,如果選擇的是無界對列的話,就沒有最大線程池這一說了,也就是這個參數就沒用了),如果阻塞隊列沒有滿的話,就把提交過來的任務包裝成一個隊列的節點,存儲在隊列中,如果阻塞隊列滿的話進入下一步
  3. 到這裏就開始判斷線程池的最大數量是不是全部都在工作,如果有空閒的話,就直接通過線程工廠去新建一個線程去執行任務,如果所有的線程都在工作狀態的話,就去執行下一步
  4. 到了這一步我們定義的拒絕策略就開始起作用了,根據我們定義的拒絕策略去進行執行,如此反覆的開始從頭開始。

(3)線程池的幾個師兄弟(也就是由ThreadPoolExecutor線程池演變的幾個兄弟,但是他們是由Executors來進行直接調用的):

  1. newFixedThreadPool()固定線程池的具體多少個的線程池
  2. newSingleThreadExecutor()只有一個線程的線程池
  3. newCachedThreadPool()創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程
  4. newScheduledThreadPool()適用於需要多個後臺線程執行週期任務,保證順序的執行各個任務的應用場景的

(4)線程池的幾種拒絕策略:

  1. AbortPolicy,這種策略直接拋出異常,丟棄任務。(jdk默認策略,隊列滿併線程滿時直接拒絕添加新任務,並拋出RejectedExecutionException異常
  2. DiscardPolicy,這種策略和AbortPolicy幾乎一樣,也是丟棄任務,只不過他不拋出異常
  3. DiscardOldestPolicy,這種其實是在當線程池沒有關閉的前提下,會先去丟棄掉緩存在隊列中的最早的任務
  4. CallerRunsPolicy,此策略提供簡單的反饋控制機制,能夠減緩新任務的提交速度。

補充一個問題:如果現在阻塞隊列中的任務滿了,而且這任務又必須執行,該怎麼辦?

  • 不知道面試官是不是想要考我幾種拒絕策略的用法
  • 可以實現RejectedExecutionHandler接口來進行自定義拒絕策略來完成這個任務
  • 或者

10、樂觀鎖(哪些類實現了)與悲觀鎖(有哪些具體的實現)的使用場景

使用樂觀鎖的實現:

  • CAS無鎖算法
  • StampedLock(他是在JDK1.8新加的一種來補充讀寫鎖的,他是利用的樂觀鎖的思想,但是他是不可重入鎖)

使用悲觀鎖實現的:

  • synchronize鎖
  • AQS
  • ReentrantReadWriteLock(是重入鎖)

11、阻塞隊列

  1. ArrayBlockingQueue :一個由數組結構組成的有界阻塞隊列。
  2. LinkedBlockingQueue :一個由鏈表結構組成的有界阻塞隊列。
  3. PriorityBlockingQueue :一個支持優先級排序的無界阻塞隊列。
  4. DelayQueue:一個使用優先級隊列實現的無界阻塞隊列。
  5. SynchronousQueue:一個不存儲元素的阻塞隊列。
  6. LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列。
  7. LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列。

12、 死鎖四個條件,如何避免

四個條件:

  • 互斥條件:一個資源每次只能被一個進程使用,即在一段時間內某 資源僅爲一個進程所佔有。此時若有其他進程請求該資源,則請求進程只能等待。

  • 請求與保持條件:進程已經保持了至少一個資源,但又提出了新的資源請求,而該資源
    已被其他進程佔有,此時請求進程被阻塞,但對自己已獲得的資源保持不放。

  • 不可剝奪條件:進程所獲得的資源在未使用完畢之前,不能被其他進程強行奪走,即只能 由獲得該資源的進程自己來釋放(只能是主動釋放)。

  • 循環等待條件: 若干進程間形成首尾相接循環等待資源的關係

如何進行避免死鎖:

  • 破壞“不可剝奪”條件:一個進程不能獲得所需要的全部資源時便處於等待狀態,等待期間他佔有的資源將被隱式的釋放重新加入到
    系統的資源列表中,可以被其他的進程使用,而等待的進程只有重新獲得自己原有的資源以及新申請的資源纔可以重新啓動,執行。
  • 破壞”請求與保持條件“:第一種方法靜態分配即每個進程在開始執行時就申請他所需要的全部資源。第二種是動態分配即每個進程在申請所需要的資源時他本身不佔用系統資源。
  • 破壞“循環等待”條件:採用資源有序分配其基本思想是將系統中的所有資源順序編號,將緊缺的,稀少的採用較大的編號,在申請資源時必須按照編號的順序進行,一個進程只有獲得較小編號的進程才能申請較大編號的進程。

13、進程通信方式,爲什麼要有進程?

  1. 信號量( semophore ) : 信號量是一個計數器,可以用來控制多個進程對共享資源的訪問。它常作爲一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作爲進程間以及同一進程內不同線程之間的同步手段。
  2. 共享內存( shared memory ) :共享內存就是映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創建,但多個進程都可以訪問。共享內存是最快的 IPC 方式,它是針對其他進程間通信方式運行效率低而專門設計的。它往往與其他通信機制,如信號兩,配合使用,來實現進程間的同步和通信。
  3. 消息隊列( message queue ) : 消息隊列是由消息的鏈表,存放在內核中並由消息隊列標識符標識。消息隊列克服了信號傳遞信息少、管道只能承載無格式字節流以及緩衝區大小受限等缺點。

14、java線程變量怎麼實現的?內存模型?

15、CountdownLatch和CyclicBarrier的區別和用法

  • CountdownLatch的使用場景是可以用來當測試並行(強調的是多個線程同時開始執行)開始的發令槍
  • CountdownLatch強調的是能夠使一個線程在等待另外一些線程完成各自工作之後,再繼續執行,而CyclicBarrier強調的是當所有的線程都達到同一個臨界屏障的時候(也就相當於說是所有線程都在等待最後一個線程到達),再同時去進行執行任務
  • 對比:CountdownLatch只能使用一次,而CyclicBarrier可以多次利用

16、有什麼線程安全的List?(CopyOnWriteArrayList)講一下怎麼實現線程安全的?(寫時複製,讀時共享,加鎖機制)

  • 首先CopyOnWriteArraryList在寫的時候會先複製一份集合,然後進行寫操作,這樣的話就能保證了在寫數據的時候,就算有多個線程過來,也能保證線程安全。
    其保證了每次只能有一個線程拿到複製集合的權限,其實也就是通過ReentrantLock 重入鎖進行加鎖機制。在寫完新的集合之後,會把寫完之後的集合的引用給原本的集合,此時的原本集合就是寫完之後最新的
//源碼的添加操作
 public boolean add(E e) {
        final ReentrantLock lock = this.lock;//重入鎖
        lock.lock();//加鎖啦
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);//拷貝新數組
            newElements[len] = e;
            setArray(newElements);//將引用指向新數組  1
            return true;
        } finally {
            lock.unlock();//解鎖啦
        }
    }

CopyOnWriteArrayList有什麼優缺點:

缺點:

  • 會有延遲,當如果當你在put的時候會觸發複製數組,如果集合比較大的話,那麼就會比較費時間,此時當你還沒有複製完成把複製之後的引用更新到原本的數組的時候,這時如果有線程過來讀取的話,其實還會讀取到原本的修改之前的數據
  • 比較的佔用內存,因爲如果集合數據比較多的話,底層會有一個Arrays.copyOf()方法的調用

優點:

  • 數據一致性完整,爲什麼?因爲加鎖了,併發數據不會亂
  • 解決了像ArrayList、Vector這種集合多線程遍歷迭代問題,記住,Vector雖然線程安全,只不過是加了synchronized關鍵字,迭代問題完全沒有解決!

17、atomic底層是如何實現的

在操作系統層面保證原子性有兩點:

  • 操作系統通過對處理器使用總線鎖來保證原子性,因爲如今的電腦都是多個CPU來進行並行操作的,但是會出現一個變量被多個CPU同時緩存到自己的內存中,這樣的話就會出現問題,而總線鎖其實就是爲了解決這一問題,當處理器提供一個LOCK信號的時候,當一個處理器在總線上輸出此信號時,其他處理器的請求將被阻塞住,那麼該處理器可以獨佔共享內存。
  • 使用緩存鎖保證原子性,其實就是指內存區域如果被緩存在處理器的緩存行中,並且在Lock操作期間被鎖定,那麼當它執行鎖操作回寫到內存時,處理器不在總線上聲言LOCK#信號,而是修改內部的內存地址,並允許它的緩存一致性機制來保證操作的原子性,因爲緩存一致性機制會阻止同時修改由兩個以上處理器緩存的內存區域數據,當其他處理器回寫已被鎖定的緩存行的數據時,會使緩存行無效,緩存鎖定其實就是如果緩存在處理器緩存行中內存區域在 LOCK 操作期間被鎖定
    是有兩種情況下處理器不會使用緩存鎖定。第一種情況是:當操作的數據不能被緩存在處理器內部,或操作的數據跨多個緩存行(cache line),則處理器會調用總線鎖定。第二種情況是:有些處理器不支持緩存鎖定。對於 Inter486 和奔騰處理器, 就算鎖定的內存區域在處理器的緩存行中也會調用總線鎖定

在Java中是如何保證原子一致性的:

  • 一般是使用CAS自旋無鎖算法來實現的。

底層是使用的CAS無鎖無阻塞的算法和自旋實現的

18、就算是使用了Atomic實現了高併發的原子性,那麼Atomic在很高併發場景下有什麼問題,比如缺點什麼的?

就算在高併發場景中使用到了Atomic進行原子性的增加,如果併發的數量競爭激烈的的情況下,那麼就會出現一種情況,就是線程在進行CAS的時候不斷失敗,雖然不會直接掛起線程阻塞,但是這種如果激烈的話,就會有大量的線程進行CAS失敗,導致CPU消耗很大,影響系統性能

下面我會通過一篇文章來詳細介紹一下JDK1.8把AtomicLong給優化之後的LongAdder的內部原理,(這個思想很巧妙哦,借鑑的JDK1.7中HashMap的分段鎖思想),這裏只具體拿LongAdder做分析。

19、每個線程有自己的工作線程,static的變量會被拷貝到工作內存中嗎?

  • 不會被拷貝到自己的工作內存中,因爲static的變量是存儲在JVM運行時數據中的方法區的,也就是相當於是存儲在堆中(因爲在方法區也在堆中,但是被稱爲是“非堆”)

20、對ThreadLocal做一個總結:

  • 聊到如果出現hash衝突的話可以採用線性探測法,可以扯到ThreadLocal中也是用線性探測nextIndex(i,lenth)方法去解決Hash衝突。
  • 底層也調用了CAS進行併發加一去進行對下一個桶數組進行線性探測的
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章