Java併發多線程-----最全面的面試彙總

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

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

面試題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狀態。

面試題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的理解

CAS的原理:

  • 首先CAS是一個採用的是樂觀鎖的思想進行無鎖算法實現的,其實就是(compare and swap)比較然後交換,比如CompareAndSwap(V,E,N)V就表示的是當前內存位置的值,E就表示的是預期的要比較的值,N就表示如果跟預期的值是一樣的話就把當前內存位置的值要更新的新的值,如果當前內存位置的值和預期的值不一樣的話,就不做任何操作,底層的實現是依賴於Unsafe類的方法

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()適用於需要多個後臺線程執行週期任務,保證順序的執行各個任務的應用場景的

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

使用樂觀鎖的實現:

  • CAS無鎖算法

使用悲觀鎖實現的:

  • synchronize鎖
  • AQS

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)講一下怎麼實現線程安全的?(讀時複製,寫時共享,加鎖機制)

17、atomic底層是如何實現的

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

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

  • 不會被拷貝到自己的工作內存中,因爲static的變量是存儲在JVM運行時數據中的方法區的,也就是相當於是存儲在堆中(因爲在方法區也在堆中,但是被稱爲是“非堆”)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章