Java多線程面試題

1.說說你對volatile的理解。

volatitle在多線程狀況下,能夠保證數據數據的可見性。禁止指令重排優化,從而避免多線程環境下程序出現亂序執行致使執行結果不一致的問題,它不支持原子性(使用AutomicInteger來保證原子性)。java

2.你在哪些地方使用過volatitle

在單例模式DCL中使用過。程序員

3.說說在建立線程時使用Runable接口比Thread類的好處:

(1)Thread類繼承存在單繼承的侷限性,而接口不會。
(2)Runable能夠體現數據共享的概念(JMM內存模型圖),代碼能夠被多個線程共享,代碼和數據獨立。
(3)線程池只能放入實現Runnable或callable類的線程,不能直接放入繼承Thread的類
(3)Runnable實現線程能夠對線程進行復用,由於runnable是輕量級對象,而Thread不行,它是重量級對象。
緩存

4.在多線程中爲何static和volatile一塊兒使用修飾變量,好處是什麼有何區別?

(1)static保證惟一性,就是在主內存中是惟一的變量。各個線程建立時須要從主內存同一個位置拷貝到本身工做內存中去,也就是說只能保證線程建立時,變量的值是相同來源的,運行時仍是使用各自工做內存中的值,依然會有不一樣步的問題。
(2)volatile是保證可見性,就是指在工做線程和主內存的數據的一致性,若是改變了工做線程中volatile修飾的變量,那麼主內存也要發生更新。因此,volatile和static一塊兒使用不矛盾。由於static修飾只能保證在主內存的惟一性,若是涉及到其餘工做線程,改變參數可能就會致使static修飾的變量的內容沒法同步,因此static和volatile能夠一塊兒使用,由於他們管的地方是不同的,互不影響。安全

5.說說你對CAS的理解?

CAS即比較而且交換,CPU去更新一個值,若是初始值與原來值不相同操做就失敗,但若是初始值與原來值相同就操做就成功。服務器

CAS應用:CAS有3個操做數,V:要更新的變量;E:預期值;N:新值。若是V值等於E值,則將V值設爲N值;若是V值不等於E值,說明其餘線程作了更新,那麼當前線程什麼也不作。(放棄操做或從新讀取數據)多線程

6.說說CAS的實現原理

CAS是經過Unsafe實現,Unsafe是CAS的核心類,因爲Java沒法直接訪問底層系統,須要經過本地native來訪問,Unsafe至關於
一個後門,基於該類能夠直接操做特定的內存數據。Unsafe類存在於sun.misc包中,其內部方法操做
能夠像C語言的指針同樣直接操做內存。由於Java中CAS的操做的執行依賴於Unsafe類的方法。併發

底層代碼以下框架

public final int getAndIncrement() { 
    return unsafe.getAndAddInt(this, valueOffset, 1);   
}

//Unsafe類中的getAndAddInt方法
public final int getAndAddInt(Object o, long offset, int delta) {        
    int v;        
    do {            
        v = getIntVolatile(o, offset);        
    } while (!compareAndSwapInt(o, offset, v, v + delta));        
    return v;
}

CAS的缺點
1.循環時間長,開銷很大。
2.只能保證一個共享變量的原子操做異步

7. 談談AtomicInteger的ABA問題,原子更新引用知道嗎?

假若有A,B兩條線程,線程A,B都拷貝主內存的數據到本身的內存空間。這時候B線程將本身內存中的數據更改了並更新到主內存中,而後又將本身內存中的數據改回了原來的初始值。如今A線程在作修改的時候,將本身內存中的數據與主內存中的數據作對比發現是同樣的並作更新。對與A線程而言它並不知道B線程已經作了一些相應的更改,這時候就產生了ABA問題。經過原子更新引用來解決ABA問題。函數

8. 如何解決ABA問題?

每修改一次值都對其添加版本號,對值和版本號都作CAS操做。在值和版本號都一致的狀況下才能作修改。AtomicStampedReference是一個帶有時間戳的對象引用,能很好的解決CAS機制中的ABA問題。
其實除了AtomicStampedReference類,還有一個原子類也能夠解決就是AtomicMarkableReference,它不是維護一個版本號,而是維護一個boolean類型的標記,用法沒有AtomicStampedReference靈活。所以也只是在特定的場景下使用。

 9. Collection和Collections的區別

(1)Collection 是一個集合接口。它提供了對集合對象進行基本操做的通用接口方法。Collection接口在Java 類庫中有不少具體的實現。Collection接口的意義是爲各類具體的集合提供了最大化的統一操做方式。

(2)Collections 是一個包裝類。它包含有各類有關集合操做的靜態多態方法。此類不能實例化,就像一個工具類,服務於Java的Collection框架。

 10.說說你遇到的線程不安全問題,以及解決方式

致使緣由:線程不安全問題,基本都是因爲多線程併發爭搶修改數據致使的。
故障現象:會拋出java.util.ConcurrentModificationException異常

(1)ArrayList線程不安全解決方案

a. 用Vector來解決,
b. 用Collections.synchronizedList(new ArrayList<>())
c. new CopyOnWriteArrayList<>()

(2)HashSet線程不安全解決方案

a. 用Collections.synchronizedSet(new HashSet<>())來解決
說下HashSet的底層實現
HshSet底層是HashMap,既然是HashMap爲何咱們add的時候只添加一個值,而不以key-value的形式添加呢。由於底層的HashMap已經put了Value值,這個Value值實際上是一個Object對象。咱們在調用HashSet的add方法時等於只是添加了一個key值。這也是set集合不重複的緣由。

(3)HashMap線程不安全解決方案

a. 用Collections.synchronizedMap(new HashMap<>())
b. new ConcurrentHashMap<>()

11 . 說一下公平鎖與非公平鎖,二者之間的區別

公平鎖:指多個線程按照申請鎖的順序來獲取鎖,相似排隊購票,先到先得。
非公平鎖:指多個線程獲取鎖的順序並非按照申請鎖的順序,有可能後申請的線程比先申請的線程優先獲取鎖,在高併發狀況下,有可能形成優先級反轉或者飢餓現象。

併發包中的ReentrantLock的建立能夠指定構造函數的boolean類型來獲得公平鎖或非公平鎖,默認是非公平鎖。

關於二者的區別:
公平鎖:就是很公平,在併發環境中,每一個線程獲取鎖時會先看此鎖維護的等待隊列,若是爲空,或者當前線程是等待隊列的第一個,就佔有鎖。不然就會加入到等待隊列中,後面按照FIFO的規則從隊列中取到本身。 

非公平鎖:比較粗魯,上來就嘗試直接佔有鎖,若是嘗試失敗,就採用相似公平鎖的方式。相對來講,非公平鎖會有更好的性能,由於它的吞吐量比較大。固然,非公平鎖讓獲取鎖的時間變得更加不肯定,可能會致使在阻塞隊列中的線程長期處於飢餓狀態。

說明:

對於Java ReentrantLock而言,經過構造函數指定該鎖是不是公平鎖,默認是非公平鎖。非公平鎖的優勢在於吞吐量比公平鎖大。

對於Synchronized而言,也是一種非公平鎖。

12. 說說你對可重入鎖的理解

可重入鎖又名遞歸鎖,一個線程拿到一個方法的一把鎖能夠訪問其內部代碼,可是其內部還嵌套了另一個方法,這個方法也上了鎖。由於線程拿到了外層方法的鎖,這把鎖和嵌套方法的鎖是同一把鎖,因此能夠直接訪問嵌套方法內部。像ReentrantLock,Synchronized就是典型的非公平的可重入鎖。可重入鎖最大的做用就是能夠避免死鎖。

可重入鎖代碼實例

package com.example.thread; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ReentryLock{ /** * 對於 ReentrantLock 可重入鎖 */
    private Lock lock=new ReentrantLock(); private void getInfo(){ try { lock.lock(); System.out.println(Thread.currentThread().getName()+"==獲取信息"); getDetails(); } finally{ lock.unlock(); } } private void getDetails(){ try { lock.lock(); System.out.println(Thread.currentThread().getName()+"==獲取詳情"); } catch (Exception e) { e.printStackTrace(); }finally{ lock.unlock(); } } /** * 對於synchronized可重入鎖 */
    private synchronized void getSMS(){ System.out.println(Thread.currentThread().getName()+"==開始發送短信!"); getEmail(); } private synchronized void getEmail(){ System.out.println(Thread.currentThread().getName()+"==開始發送郵件!"); } public static void main(String[] args) { ReentryLock reentryLock=new ReentryLock(); new Thread(()->{ reentryLock.getSMS(); },"t1").start(); new Thread(()->{ reentryLock.getInfo(); },"t2").start(); } }

13. 說說你對自旋鎖(Unsafe+CAS思想)的理解

是指讀取鎖的線程不會當即阻塞,而是採用循環的方式去嘗試獲取鎖,這樣的好處是減小線程上下文切換的消耗。
缺點是循環會消耗CPU 

package com.example.thread;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

public class SpinLock {

    private AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void lock() {
        Thread current = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"\t into lock");
        while (!atomicReference.compareAndSet(null, current)) {
        }
    }

    public void unlock() {
        Thread current = Thread.currentThread();
        atomicReference.compareAndSet(current, null);
        System.out.println(Thread.currentThread().getName()+"\t leave lock");
    }
    public static void main(String[] args) throws InterruptedException {
        SpinLock spinLock = new SpinLock();
        new Thread(()->{
            spinLock.lock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (Exception e) {
                e.printStackTrace();
            }finally{
                spinLock.unlock();
            }
        },"thread1").start();
        
        TimeUnit.SECONDS.sleep(1);
        new Thread(()->{
            spinLock.lock();
            spinLock.unlock();
        },"thread2").start();
    }
}

14 .獨佔鎖與共享鎖,二者有什麼區別?

獨佔鎖:指該鎖一次只能被一個線程所持有。對ReentrantLock和Synchronized而言都是獨佔鎖
共享鎖:指該鎖可被多個線程鎖持有

15. CountDownLatch/CyclicBarrier/Semaphore 使用過嗎?介紹一下。

CountDownLatch:當一個或多個線程經過調用await方法進入阻塞狀態,等待另一些線程完成各自工做以後,再繼續執行。使用一個計數器進行實現,計數器初始值爲線程的數量。當每個線程調用countDown方法完成本身任務後,計數器的值就會減一。當計數器的值爲0時,表示全部的線程都已經完成一些任務,而後喚醒哪些由於調用了await而阻塞的線程去執行接下來的任務。

CyclicBarrier:它的功能是:讓一組線程到達一個屏障時被阻塞,直到最後一個線程到達屏障,這時全部被屏障攔截的線程纔會繼續執行。它經過調用await方法讓線程進入屏障。

Semaphore:信號量主要用於兩個目的,一個是用於多個共享資源的互斥使用,另外一個用於併發線程的控制(就是控制同時獲得共享資源的線程數量)。

16. 說說CountDownlatch和CyclicBarrier以及Semaphor的區別

(1)CountDownLatch是作減法,CyclicBarrier是作加法,Semaphor的臨界資源能夠反覆使用。

(2)CountDownLatch不能重置計數,CycliBarrier提供的reset()方法能夠重置計數,不過只能等到第一個計數結束。Semaphor能夠重複使用。

(3)CountDownLatch和CycliBarrier不能控制併發線程的數量,Semaphor能夠實現控制併發線程的數量。

17 .阻塞隊列知道嗎?介紹一下。

阻塞隊列首先是一個隊列。
(1)當阻塞隊列是空的時候,從隊列中獲取元素的操做將被阻塞。
(2)當隊列是滿的時候,往隊列裏添加元素的操做會被阻塞。

18. 阻塞隊列的優勢是什麼?它主要用在哪裏?

優勢:咱們不須要關心何時須要阻塞線程,何時須要喚醒線程,由於這一切BlockingQueue都給你一手包辦了。
在concurrent包發佈之前。在多線程環境下,咱們每一個程序員都必須去本身控制這些細節,尤爲還要兼顧效率和線程
安全,而這會給咱們的程序帶來不小的複雜度。

用途:a.生產者消費者模式  b.線程池   c.消息中間件

19.多線程爲何要用 while 來判斷而不用 if 呢?

用if會形成虛假喚醒 , 詳情能夠百度。

20 . 使用condition作線程通訊,比傳統的Object的wait()、notify()實現線程間的協做有什麼好處?

Condition是在java 1.5中才出現的,它用來替代傳統的Object的wait()、notify()實現線程間的協做,相比使用Object的wait()、notify(),使用Condition的await()、signal()這種方式實現線程間協做更加安全和高效。所以一般來講比較推薦使用Condition,阻塞隊列其實是使用了Condition來模擬線程間協做。

21 . Synchronized 和lock有什麼區別?用新的lock有什麼好處?你舉例說說

(1)原始構成:synchronized是JVM層面的,底層經過monitorenter和monitorexit來實現的。Lock是JDK API層面的。(synchronized一個enter會有兩個exit,一個是正常退出,一個是異常退出(保證確定能夠退出))
(2)使用方法:synchronized不須要手動釋放鎖,而Lock須要手動釋放,若沒有主動釋放鎖就可能致使出現死鎖現象。
(3)是否可中斷:synchronized不可中斷,除非拋出異常或者正常運行完成。Lock是可中斷的。
  a.設置超時方法tryLock(long timeout,TimeUnit unit);
  b. lockInterruptibly()方法放代碼塊中,調用interrupt()
(4)是否爲公平鎖:synchronized只能是非公平鎖,而ReentrantLock既能是公平鎖,又能是非公平鎖。構造方法傳入false/true,默認是非公平鎖false。
(5)綁定多個條件Condition:synchronized不能,只能隨機喚醒。而Lock能夠經過Condition來綁定多個條件,精確喚醒。

22. 線程池的使用及優點,爲何要使用線程池?

線程池作的工做主要是控制運行的線程的數量,處理過程當中將任務放入隊列,而後在線程建立後啓動這些任務,若是線程數量超過了最大數量,則超出數量的線程排隊等候,等待其餘線程執行完畢,再從隊列中取出任務來執行。
特色:線程複用;控制最大併發數;管理線程。

優點:

1.下降資源消耗。經過重複利用已經重建的線程下降線程建立和銷燬形成的消耗。
2.提升響應速度。當任務到達時,任務能夠不須要等到線程建立就能當即執行。
3.提升線程的可管理性。線程是稀缺資源,若是無限制的建立,不只會消耗系統資源,還會下降系統的穩定性,使用線程池能夠進行統一的分配,調優和監控。

23 .說出你知道的建立線程池的幾種方式

Java中線程池是經過Executor框架實現的,該框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor這幾個類。 
Executors.newFixedThreadPool(int) 建立一個可重用固定個數的線程池,以共享的無界隊列方式來運行這些線程。
Executors.newSingleThreadPool() 建立一個單線程化的線程池,它只會用惟一的工做線程來執行任務,保證全部任務按照指定順序(FIFO, LIFO, 優先級)執行。
Executors.newScheduledThreadPool(int n) 建立一個定長線程池,支持定時及週期性任務執行。
Executors.newCachedThreadPool() 可緩存線程池,先查看池中有沒有之前創建的線程,若是有,就直接使用。若是沒有,就建一個新的線程加入池中,緩存型池子一般用於執行一些生存期很短的異步型任務。

24 .說說線程池的7大參數和底層原理   

7大參數
int corePoolSize:線程池中核心線程數的最大值
int maximumPoolSize:線程池中能擁有最多線程數
long keepAliveTime:表示空閒線程的存活時間。當線程空閒時間達到keepAliveTime,該線程會退出,直到線程數量等於corePoolSize。
TimeUnit unit:表示keepAliveTime的單位
BlockingQueue<Runnable> workQueue:用於緩存任務的阻塞隊列,被提交但還沒有被執行的任務
ThreadFactory threadFactory:用於生成線程池中工做線程的線程工廠,用於建立線程通常用默認的便可。
RejectedExecutionHandler handler: 拒絕策略,表示當隊列滿了而且工做線程大於等於線程池的最大線程數時如何來拒絕。

底層工做原理
1.當經過線程池建立好線程以後,當任務請求過來時會被核心線程去處理。
2.隨着請求的增多,若是核心線程已經被佔滿了沒有時間去處理過來的請求,這時候會將這些請求放到阻塞隊列中等待。
3.若是請求還在進一步增多,阻塞隊列的空間都已經被佔滿了。這時候會開啓新的線程直到線程數達到線程池中能擁有最多線程數,去處理請求。
4.若是請求仍是進一步增多,阻塞隊列也滿了。而且工做線程等於線程池的最大線程數,這時候會啓用拒絕策略。

25 .談談線程池的拒絕策略

等待隊列已經滿了再也塞不下新任務了,同時線程池中的最大線程數也達到了,沒法繼續爲新任務服務,這時候咱們就須要拒絕策略機制合理的處理這個問題。
四種拒絕策略:
AbortPolicy - 丟棄任務,並拋出拒絕執行 RejectedExecutionException 異常信息。線程池默認的拒絕策略。
必須處理好拋出的異常,不然會打斷當前的執行流程,影響後續的任務執行。
CallerRunsPolicy - "調用者運行"一種調用機制,該策略既不會拋棄任務,也不會拋出異常,而是將某些任務回退到調用者,從而下降新任務的流量。
可是,因爲調用者本身運行任務,若是任務提交速度過快,可能致使程序阻塞,性能效率上必然的損失較大
DiscardPolicy - 直接丟棄,不予任何處理也不拋出異常。
DiscardOldestPolicy - 當觸發拒絕策略,只要線程池沒有關閉的話,丟棄阻塞隊列 workQueue 中最老的一個任務,並將新任務加入隊列中嘗試再次提交新任務。

26. 你在工做中哪一種線程池用的最多?Executors已經提供了線程池爲何不用?

JDK 提供的線程池一個都不用。
1.newFixedThreadPool()、newSingleThreadExecutor() 底層代碼 中 LinkedBlockingQueue 沒有設置容量大小,默認容許的請求隊列長度是 Integer.MAX_VALUE, 能夠認爲是無界的。線程池中 多餘的線程會被緩存到 LinkedBlockingQueue中,最終內存撐爆。

2.newCachedThreadPool()、newScheduledThreadPool() 的 底層代碼中的最大線程數(maximumPoolSize) 是Integer.MAX_VALUE,能夠認爲是無限大,若是線程池中,執行中的線程沒有及時結束,而且不斷地有線程加入並執行,最終會將內存撐爆。
建議經過ThreadPoolExecutor去建立線程池。

27. 如何考慮配置一個合理的線程池,或者說線程池如何設置合理的參數?

我會根據個人業務是CPU密集型仍是IO密集型來作決定。
CPU密集型:意思是該任務須要大量的運算,而沒有阻塞,CPU一直全速運行。CPU密集任務只有在真正的多核CPU上纔可能獲得加速(經過多線程)。而在單核CPU上,不管你開幾個模擬的多線程該任務都不可能獲得加速,由於CPU總的運算能力就那些。CPU密集型任務配置儘量少的線程數量。
通常公式: CPU核數+1個線程的線程池

IO密集型:即任務須要大量的IO,即大量的阻塞。IO密集型時,大部分線程都阻塞,故須要都配置線程數:

參考公式: CPU核數/1-阻塞係數 阻塞係數在0.8-0.9之間。
好比8核CPU:8/1-0.9=80個線程數

注意:必須熟悉本身的硬件,看服務器是4核仍是8核

28. 說說多線程中產生死鎖的緣由,如何去解決?

死鎖就是兩個或兩個以上的線程在執行過程當中,因爲競爭資源或者因爲彼此通訊而形成的一種阻塞的現象,
若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖。

產生死鎖的緣由:
(1)競爭系統資源 (2)進程的推動順序不當 (3)資源分配不當

解決死鎖
(1)jps命令定位進程號 (2)jstack找到死鎖查看

相關文章
相關標籤/搜索