單懟多線程,100到面試題,你能答上幾個?(附答案)

如今的面試對程序員要求愈來愈高,基礎越紮實拿offer的機率就越高,大廠對基礎仍是很執着的,若是你基礎功底好,面試官是會看中你的潛力,而後去培養的你的。java

除了算法、網絡、計算機基礎等,多線程愈來愈被注重。廢話很少說,直接把題分享給你們。程序員

1.什麼是進程?

進程是系統中正在運行的一個程序,程序一旦運行就是進程。面試

進程能夠當作程序執行的一個實例。進程是系統資源分配的獨立實體,每一個進程都擁有獨立的地址空間。一個進程沒法訪問另外一個進程的變量和數據結構,若是想讓一個進程訪問另外一個進程的資源,須要使用進程間通訊,好比管道,文件,套接字等。算法

2.什麼是線程?

是操做系統可以進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運做單位。一條線程指的是進程中一個單一順序的控制流,一個進程中能夠併發多個線程,每條線程並行執行不一樣的任務。sql

3.線程的實現方式?

1.繼承Thread類編程

2.實現Runnable接口數組

3.使用Callable和Future安全

4.Thread 類中的start() 和 run() 方法有什麼區別?

1.start()方法來啓動線程,真正實現了多線程運行。這時無需等待run方法體代碼執行完畢,能夠直接繼續執行下面的代碼;經過調用Thread類的start()方法來啓動一個線程, 這時此線程是處於就緒狀態, 並無運行。 而後經過此Thread類調用方法run()來完成其運行操做的, 這裏方法run()稱爲線程體,它包含了要執行的這個線程的內容, Run方法運行結束, 此線程終止。而後CPU再調度其它線程。 2.run()方法看成普通方法的方式調用。程序仍是要順序執行,要等待run方法體執行完畢後,纔可繼續執行下面的代碼; 程序中只有主線程——這一個線程, 其程序執行路徑仍是隻有一條, 這樣就沒有達到寫線程的目的。bash

5.線程NEW狀態

new建立一個Thread對象時,並沒處於執行狀態,由於沒有調用start方法啓動改線程,那麼此時的狀態就是新建狀態。網絡

6.線程RUNNABLE狀態

線程對象經過start方法進入runnable狀態,啓動的線程不必定會當即獲得執行,線程的運行與否要看cpu的調度,咱們把這個中間狀態叫可執行狀態(RUNNABLE)。

7.線程的RUNNING狀態

一旦cpu經過輪詢貨其餘方式從任務能夠執行隊列中選中了線程,此時它才能真正的執行本身的邏輯代碼。

8.線程的BLOCKED狀態

線程正在等待獲取鎖。

  • 進入BLOCKED狀態,好比調用了sleep,或者wait方法
  • 進行某個阻塞的io操做,好比因網絡數據的讀寫進入BLOCKED狀態
  • 獲取某個鎖資源,從而加入到該鎖的阻塞隊列中而進入BLOCKED狀態

9.線程的TERMINATED狀態

TERMINATED是一個線程的最終狀態,在該狀態下線程不會再切換到其餘任何狀態了,表明整個生命週期都結束了。

下面幾種狀況會進入TERMINATED狀態:

  • 線程運行正常結束,結束生命週期
  • 線程運行出錯意外結束
  • JVM Crash 致使全部的線程都結束

10.線程狀態轉化圖

image-20200501131131886

11.i——與System.out.println()的異常

示例代碼:

public class XkThread extends Thread {

    private int i = 5;

    @Override
    public void run() {
       System.out.println("i=" + (i——————) + " threadName=" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        XkThread xk = new XkThread();
        Thread t1 = new Thread(xk);
        Thread t2 = new Thread(xk);
        Thread t3 = new Thread(xk);
        Thread t4 = new Thread(xk);
        Thread t5 = new Thread(xk);

        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

複製代碼

結果:

i=5 threadName=Thread-1
i=2 threadName=Thread-5
i=5 threadName=Thread-2
i=4 threadName=Thread-3
i=3 threadName=Thread-4
複製代碼

雖然println()方法在內部是同步的,但i——————的操做倒是在進入println()以前發生的,因此有發生非線程安全的機率。

println()源碼:

public void println(String x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }
複製代碼

12.如何知道代碼段被哪一個線程調用?

System.out.println(Thread.currentThread().getName());
複製代碼

13.線程活動狀態?

public class XKThread extends Thread {

    @Override
    public void run() {
        System.out.println("run run run is "  + this.isAlive() );
    }

    public static void main(String[] args) {
        XKThread xk = new XKThread();
        System.out.println("begin ——— " + xk.isAlive());
        xk.start();
        System.out.println("end ————— " + xk.isAlive());

    }
}

複製代碼

14.sleep()方法

方法sleep()的做用是在指定的毫秒數內讓當前的「正在執行的線程」休眠(暫停執行)。

15.如何優雅的設置睡眠時間?

jdk1.5 後,引入了一個枚舉TimeUnit,對sleep方法提供了很好的封裝。

好比要表達2小時22分55秒899毫秒。

Thread.sleep(8575899L);
TimeUnit.HOURS.sleep(3);
TimeUnit.MINUTES.sleep(22);
TimeUnit.SECONDS.sleep(55);
TimeUnit.MILLISECONDS.sleep(899);
複製代碼

能夠看到表達的含義更清晰,更優雅。

16.中止線程

run方法執行完成,天然終止。

stop()方法,suspend()以及resume()都是過時做廢方法,使用它們結果不可預期。

大多數中止一個線程的操做使用Thread.interrupt()等於說給線程打一箇中止的標記, 此方法不回去終止一個正在運行的線程,須要加入一個判斷才能能夠完成線程的中止。

17.interrupted 和 isInterrupted

interrupted : 判斷當前線程是否已經中斷,會清除狀態。

isInterrupted :判斷線程是否已經中斷,不會清除狀態。

18.yield

放棄當前cpu資源,將它讓給其餘的任務佔用cpu執行時間。但放棄的時間不肯定,有可能剛剛放棄,立刻又得到cpu時間片。

測試代碼:(cpu獨佔時間片)

public class XKThread extends Thread {

    @Override
    public void run() {
        long beginTime = System.currentTimeMillis();
        int count = 0;
        for (int i = 0; i < 50000000; i++) {
            count = count + (i + 1);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("用時 = " + (endTime - beginTime) + " 毫秒! ");
    }

    public static void main(String[] args) {
        XKThread xkThread = new XKThread();
        xkThread.start();
    }

}
複製代碼

結果:

用時 = 20 毫秒! 
複製代碼

加入yield,再來測試。(cpu讓給其餘資源致使速度變慢)

public class XKThread extends Thread {

    @Override
    public void run() {
        long beginTime = System.currentTimeMillis();
        int count = 0;
        for (int i = 0; i < 50000000; i++) {
            Thread.yield();
            count = count + (i + 1);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("用時 = " + (endTime - beginTime) + " 毫秒! ");
    }

    public static void main(String[] args) {
        XKThread xkThread = new XKThread();
        xkThread.start();
    }

}

複製代碼

結果:

用時 = 38424 毫秒! 
複製代碼

19.線程的優先級

在操做系統中,線程能夠劃分優先級,優先級較高的線程獲得cpu資源比較多,也就是cpu有限執行優先級較高的線程對象中的任務,可是不能保證必定優先級高,就先執行。

Java的優先級分爲1~10個等級,數字越大優先級越高,默認優先級大小爲5。超出範圍則拋出:java.lang.IllegalArgumentException。

20.優先級繼承特性

線程的優先級具備繼承性,好比a線程啓動b線程,b線程與a優先級是同樣的。

21.誰跑的更快?

設置優先級高低兩個線程,累加數字,看誰跑的快,上代碼。

public class Run extends Thread{

    public static void main(String[] args) {
        try {
            ThreadLow low = new ThreadLow();
            low.setPriority(2);
            low.start();

            ThreadHigh high = new ThreadHigh();
            high.setPriority(8);
            high.start();

            Thread.sleep(2000);
            low.stop();
            high.stop();
            System.out.println("low = " + low.getCount());
            System.out.println("high = " + high.getCount());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}

class ThreadHigh extends Thread {
    private int count = 0;

    public int getCount() {
        return count;
    }


    @Override
    public void run() {
        while (true) {
            count++;
        }
    }
}

class ThreadLow extends Thread {
    private int count = 0;

    public int getCount() {
        return count;
    }


    @Override
    public void run() {
        while (true) {
            count++;
        }
    }
}
複製代碼

結果:

low  = 1193854568
high = 1204372373

複製代碼

22.線程種類

Java線程有兩種,一種是用戶線程,一種是守護線程。

23.守護線程的特色

守護線程是一個比較特殊的線程,主要被用作程序中後臺調度以及支持性工做。當Java虛擬機中不存在非守護線程時,守護線程纔會隨着JVM一同結束工做。

24.Java中典型的守護線程

GC(垃圾回收器)

25.如何設置守護線程

Thread.setDaemon(true)

PS:Daemon屬性須要再啓動線程以前設置,不能再啓動後設置。

25.Java虛擬機退出時Daemon線程中的finally塊必定會執行?

Java虛擬機退出時Daemon線程中的finally塊並不必定會執行。

代碼示例:

public class XKDaemon {
    public static void main(String[] args) {
        Thread thread = new Thread(new DaemonRunner(),"xkDaemonRunner");
        thread.setDaemon(true);
        thread.start();

    }

    static class DaemonRunner implements Runnable {

        @Override
        public void run() {
            try {
                SleepUtils.sleep(10);
            } finally {
                System.out.println("Java小咖秀 daemonThread finally run …");
            }

        }
    }
}
複製代碼

結果:

複製代碼

沒有任何的輸出,說明沒有執行finally。

26.設置線程上下文類加載器

​ 獲取線程上下文類加載器

public ClassLoader getContextClassLoader() 複製代碼

​ 設置線程類加載器(能夠打破Java類加載器的父類委託機制)

public void setContextClassLoader(ClassLoader cl) 複製代碼

27.join

join是指把指定的線程加入到當前線程,好比join某個線程a,會讓當前線程b進入等待,直到a的生命週期結束,此期間b線程是處於blocked狀態。

28.什麼是synchronized?

synchronized關鍵字能夠時間一個簡單的策略來防止線程干擾和內存一致性錯誤,若是一個對象是對多個線程可見的,那麼對該對想的全部讀寫都將經過同步的方式來進行。

29.synchronized包括哪兩個jvm重要的指令?

monitor enter 和 monitor exit

30.synchronized關鍵字用法?

能夠用於對代碼塊或方法的修飾

31.synchronized鎖的是什麼?

普通同步方法 —————> 鎖的是當前實力對象。

靜態同步方法—————> 鎖的是當前類的Class對象。

同步方法快 —————> 鎖的是synchonized括號裏配置的對象。

32.Java對象頭

synchronized用的鎖是存在Java對象頭裏的。對象若是是數組類型,虛擬機用3個字寬(Word)存儲對象頭,若是對象是非數組類型,用2字寬存儲對象頭。

Tips:32位虛擬機中一個字寬等於4字節。

33.Java對象頭長度

image-20200418215447539

34.Java對象頭的存儲結構

32位JVM的Mark Word 默認存儲結構

image-20200418220122794

35.Mark Word的狀態變化

Mark Word 存儲的數據會隨着鎖標誌爲的變化而變化。

image-20200418220322880

64位虛擬機下,Mark Word是64bit大小的

image-20200418224055558

36.鎖的升降級規則

Java SE 1.6 爲了提升鎖的性能。引入了「偏向鎖」和輕量級鎖「。

Java SE 1.6 中鎖有4種狀態。級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態。

鎖只能升級不能降級。

37.偏向鎖

大多數狀況,鎖不只不存在多線程競爭,並且總由同一線程屢次得到。當一個線程訪問同步塊並獲取鎖時,會在對象頭和棧幀中記錄存儲鎖偏向的線程ID,之後該線程在進入和退出同步塊時不須要進行 cas操做來加鎖和解鎖,只需測試一下對象頭 Mark Word裏是否存儲着指向當前線程的偏向鎖。若是測試成功,表示線程已經得到了鎖,若是失敗,則須要測試下Mark Word中偏向鎖的標示是否已經設置成1(表示當前時偏向鎖),若是沒有設置,則使用cas競爭鎖,若是設置了,則嘗試使用cas將對象頭的偏向鎖只想當前線程。

38.關閉偏向鎖延遲

java6和7中默認啓用,可是會在程序啓動幾秒後才激活,若是須要關閉延遲,

-XX:BiasedLockingStartupDelay=0。

39.如何關閉偏向鎖

JVM參數關閉偏向鎖:-XX:-UseBiasedLocking=false,那麼程序默認會進入輕量級鎖狀態。

Tips:若是你能夠肯定程序的全部鎖一般狀況處於競態,則能夠選擇關閉。

40.輕量級鎖

線程在執行同步塊,jvm會如今當前線程的棧幀中建立用於儲存鎖記錄的空間。並將對象頭中的Mark Word複製到鎖記錄中。而後線程嘗試使用cas將對象頭中的Mark Word替換爲之鄉鎖記錄的指針。若是成功,當前線程得到鎖,若是失敗,表示其餘線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。

41.輕量鎖的解鎖

輕量鎖解鎖時,會使原子操做cas將 displaced Mark Word 替換回對象頭,若是成功則表示沒有競爭發生,若是失敗,表示存在競爭,此時鎖就會膨脹爲重量級鎖。

42.鎖的優缺點對比

image-20200419110938271

43.什麼是原子操做

不可被中斷的一個或一系列操做

44.Java如何實現原子操做

Java中經過鎖和循環cas的方式來實現原子操做,JVM的CAS操做利用了處理器提供的CMPXCHG指令來實現的。自旋CAS實現的基本思路就是循環進行CAS操做直到成功爲止。

45.CAS實現原子操做的3大問題

ABA問題,循環時間長消耗資源大,只能保證一個共享變量的原子操做

46.什麼是ABA問題

問題:

由於cas須要在操做值的時候,檢查值有沒有變化,若是沒有變化則更新,若是一個值原來是A,變成了B,又變成了A,那麼使用cas進行檢測時會發現發的值沒有發生變化,實際上是變過的。

解決:

添加版本號,每次更新的時候追加版本號,A-B-A —> 1A-2B-3A。

從jdk1.5開始,Atomic包提供了一個類AtomicStampedReference來解決ABA的問題。

47.CAS循環時間長佔用資源大問題

若是jvm能支持處理器提供的pause指令,那麼效率會有必定的提高。

1、它能夠延遲流水線執行指令(de-pipeline),使cpu不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,有些處理器延遲時間是0。

2、它能夠避免在退出循環的時候因內存順序衝突而引發的cpu流水線被清空,從而提升cpu執行效率。

48.CAS只能保證一個共享變量原子操做

1、對多個共享變量操做時,能夠用鎖。

2、能夠把多個共享變量合併成一個共享變量來操做。好比,x=1,k=a,合併xk=1a,而後用cas操做xk。

Tips:java 1.5開始,jdk提供了AtomicReference類來保證飲用對象之間的原子性,就能夠把多個變量放在一個對象來進行cas操做。

49.volatile關鍵字

volatile 是輕量級的synchronized,它在多處理器開發中保證了共享變量的「可見性「。

Java語言規範第3版對volatile定義以下,Java容許線程訪問共享變量,爲了保證共享變量能準確和一致的更新,線程應該確保排它鎖單獨得到這個變量。若是一個字段被聲明爲volatile,Java線程內存模型全部線程看到這個變量的值是一致的。

50.等待/通知機制

一個線程修改了一個對象的值,而另外一個線程感知到了變化,而後進行相應的操做。

WechatIMG360
51.wait

方法wait()的做用是使當前執行代碼的線程進行等待,wait()是Object類通用的方法,該方法用來將當前線程置入「預執行隊列」中,並在 wait()所在的代碼處中止執行,直到接到通知或中斷爲止。

在調用wait以前線程須要得到該對象的對象級別的鎖。代碼體現上,即只能是同步方法或同步代碼塊內。調用wait()後當前線程釋放鎖。

52.notify

notify()也是Object類的通用方法,也要在同步方法或同步代碼塊內調用,該方法用來通知哪些可能燈光該對象的對象鎖的其餘線程,若是有多個線程等待,則隨機挑選出其中一個呈wait狀態的線程,對其發出 通知 notify,並讓它等待獲取該對象的對象鎖。

53.notify/notifyAll

notify等於說將等待隊列中的一個線程移動到同步隊列中,而notifyAll是將等待隊列中的全部線程所有移動到同步隊列中。

54.等待/通知經典範式

等待

synchronized(obj) {
		while(條件不知足) {
				obj.wait();
		}
		執行對應邏輯
}
複製代碼

通知

synchronized(obj) {
	  改變條件
		obj.notifyAll();
}
複製代碼

55.ThreadLocal

主要解決每個線程想綁定本身的值,存放線程的私有數據。

56.ThreadLocal使用

獲取當前的線程的值經過get(),設置set(T) 方式來設置值。

public class XKThreadLocal {

    public static ThreadLocal threadLocal = new ThreadLocal();

    public static void main(String[] args) {
        if (threadLocal.get() == null) {
            System.out.println("未設置過值");
            threadLocal.set("Java小咖秀");
        }
        System.out.println(threadLocal.get());
    }

}
複製代碼

輸出:

未設置過值
Java小咖秀
複製代碼

Tips:默認值爲null

57.解決get()返回null問題

經過繼承重寫initialValue()方法便可。

代碼實現:

public class ThreadLocalExt extends ThreadLocal{

    static ThreadLocalExt threadLocalExt = new ThreadLocalExt();

    @Override
    protected Object initialValue() {
        return "Java小咖秀";
    }

    public static void main(String[] args) {
        System.out.println(threadLocalExt.get());
    }
}

複製代碼

輸出結果:

Java小咖秀
複製代碼

58.Lock接口

鎖能夠防止多個線程同時共享資源。Java5前程序是靠synchronized實現鎖功能。Java5以後,併發包新增Lock接口來實現鎖功能。

59.Lock接口提供 synchronized不具有的主要特性

image-20200419193439709

60.重入鎖 ReentrantLock

支持重進入的鎖,它表示該鎖可以支持一個線程對資源的重複加鎖。除此以外,該鎖的還支持獲取鎖時的公平和非公平性選擇。

61.重進入是什麼意思?

重進入是指任意線程在獲取到鎖以後可以再次獲鎖而不被鎖阻塞。

該特性主要解決如下兩個問題:

1、鎖須要去識別獲取鎖的線程是否爲當前佔據鎖的線程,若是是則再次成功獲取。

2、所得最終釋放。線程重複n次是獲取了鎖,隨後在第n次釋放該鎖後,其餘線程可以獲取到該鎖。

62.ReentrantLock默認鎖?

默認非公平鎖

代碼爲證:

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
複製代碼

63.公平鎖和非公平鎖的區別

公平性與否針對獲取鎖來講的,若是一個鎖是公平的,那麼鎖的獲取順序就應該符合請求的絕對時間順序,也就是FIFO。

64.讀寫鎖

讀寫鎖容許同一時刻多個讀線程訪問,可是寫線程和其餘寫線程均被阻塞。讀寫鎖維護一個讀鎖一個寫鎖,讀寫分離,併發性獲得了提高。

Java中提供讀寫鎖的實現類是ReentrantReadWriteLock。

65.LockSupport工具

定義了一組公共靜態方法,提供了最基本的線程阻塞和喚醒功能。

image-20200419223303672

66.Condition接口

提供了相似Object監視器方法,與 Lock配合使用實現等待/通知模式。

67.Condition使用

代碼示例:

public class XKCondition {
    Lock lock = new ReentrantLock();
    Condition cd = lock.newCondition();

    public void await() throws InterruptedException {
        lock.lock();
        try {
            cd.await();//至關於Object 方法中的wait()
        } finally {
            lock.unlock();
        }
    }

    public void signal() {
        lock.lock();
        try {
            cd.signal(); //至關於Object 方法中的notify()
        } finally {
            lock.unlock();
        }
    }
  
}

複製代碼

68.ArrayBlockingQueue?

一個由數據支持的有界阻塞隊列,此隊列FIFO原則對元素進行排序。隊列頭部在隊列中存在的時間最長,隊列尾部存在時間最短。

69.PriorityBlockingQueue?

一個支持優先級排序的無界阻塞隊列,但它不會阻塞數據生產者,而只會在沒有可消費的數據時,阻塞數據的消費者。

70.DelayQueue?

是一個支持延時獲取元素的使用優先級隊列的實現的無界阻塞隊列。隊列中的元素必須實現Delayed接口和 Comparable接口,在建立元素時能夠指定多久才能從隊列中獲取當前元素。

71.Java併發容器,你知道幾個?

ConcurrentHashMap、CopyOnWriteArrayList 、CopyOnWriteArraySet 、ConcurrentLinkedQueue、

ConcurrentLinkedDeque、ConcurrentSkipListMap、ConcurrentSkipListSet、ArrayBlockingQueue、

LinkedBlockingQueue、LinkedBlockingDeque、PriorityBlockingQueue、SynchronousQueue、

LinkedTransferQueue、DelayQueue

72.ConcurrentHashMap

併發安全版HashMap,java7中採用分段鎖技術來提升併發效率,默認分16段。Java8放棄了分段鎖,採用CAS,同時當哈希衝突時,當鏈表的長度到8時,會轉化成紅黑樹。(如需瞭解細節,見jdk中代碼)

73.ConcurrentLinkedQueue

基於連接節點的無界線程安全隊列,它採用先進先出的規則對節點進行排序,當咱們添加一個元素的時候,它會添加到隊列的尾部,當咱們獲取一個元素時,它會返回隊列頭部的元素。它採用cas算法來實現。(如需瞭解細節,見jdk中代碼)

74.什麼是阻塞隊列?

阻塞隊列是一個支持兩個附加操做的隊列,這兩個附加操做支持阻塞的插入和移除方法。

一、支持阻塞的插入方法:當隊列滿時,隊列會阻塞插入元素的線程,直到隊列不滿。

二、支持阻塞的移除方法:當隊列空時,獲取元素的線程會等待隊列變爲非空。

75.阻塞隊列經常使用的應用場景?

經常使用於生產者和消費者場景,生產者是往隊列裏添加元素的線程,消費者是從隊列裏取元素的線程。阻塞隊列正好是生產者存放、消費者來獲取的容器。

76.Java裏的阻塞的隊列

ArrayBlockingQueue:    數組結構組成的 |有界阻塞隊列
LinkedBlockingQueue:   鏈表結構組成的|有界阻塞隊列
PriorityBlockingQueue:  支持優先級排序|無界阻塞隊列
DelayQueue:            優先級隊列實現|無界阻塞隊列
SynchronousQueue:      不存儲元素| 阻塞隊列
LinkedTransferQueue:   鏈表結構組成|無界阻塞隊列
LinkedBlockingDeque:   鏈表結構組成|雙向阻塞隊列
複製代碼

77.Fork/Join

java7提供的一個用於並行執行任務的框架,把一個大任務分割成若干個小任務,最終彙總每一個小任務結果的後獲得大任務結果的框架。

78.工做竊取算法

是指某個線程從其餘隊列裏竊取任務來執行。當大任務被分割成小任務時,有的線程可能提早完成任務,此時閒着不如去幫其餘沒完成工做線程。此時能夠去其餘隊列竊取任務,爲了減小競爭,一般使用雙端隊列,被竊取的線程從頭部拿,竊取的線程從尾部拿任務執行。

79.工做竊取算法的有缺點

優勢:充分利用線程進行並行計算,減小了線程間的競爭。

缺點:有些狀況下仍是存在競爭,好比雙端隊列中只有一個任務。這樣就消耗了更多資源。

80.Java中原子操做更新基本類型,Atomic包提供了哪幾個類?

AtomicBoolean:原子更新布爾類型

AtomicInteger:原子更新整形

AtomicLong:原子更新長整形

81.Java中原子操做更新數組,Atomic包提供了哪幾個類?

AtomicIntegerArray: 原子更新整形數據裏的元素

AtomicLongArray: 原子更新長整形數組裏的元素

AtomicReferenceArray: 原子更新飲用類型數組裏的元素

AtomicIntegerArray: 主要提供原子方式更新數組裏的整形

82.Java中原子操做更新引用類型,Atomic包提供了哪幾個類?

若是原子須要更新多個變量,就須要用引用類型了。

AtomicReference : 原子更新引用類型

AtomicReferenceFieldUpdater: 原子更新引用類型裏的字段。

AtomicMarkableReference: 原子更新帶有標記位的引用類型。標記位用boolean類型表示,構造方法時AtomicMarkableReference(V initialRef,boolean initialMark)

83.Java中原子操做更新字段類,Atomic包提供了哪幾個類?

AtomiceIntegerFieldUpdater: 原子更新整形字段的更新器

AtomiceLongFieldUpdater: 原子更新長整形字段的更新器

AtomiceStampedFieldUpdater: 原子更新帶有版本號的引用類型,將整數值

84.JDK併發包中提供了哪幾個比較常見的處理併發的工具類?

提供併發控制手段: CountDownLatch、CyclicBarrier、Semaphore

線程間數據交換: Exchanger

85.CountDownLatch

容許一個或多個線程等待其餘線程完成操做。

CountDownLatch的構造函數接受一個int類型的參數做爲計數器,你想等待n個點完成,就傳入n。

兩個重要的方法:

countDown() : 調用時,n會減1。

await() : 調用會阻塞當前線程,直到n變成0。

await(long time,TimeUnit unit) : 等待特定時間後,就不會繼續阻塞當前線程。

tips:計數器必須大於等於0,當爲0時,await就不會阻塞當前線程。

不提供從新初始化或修改內部計數器的值的功能。

86.CyclicBarrier

可循環使用的屏障。

讓一組線程到達一個屏障(也能夠叫同步點)時被阻塞,直到最後一個線程到達屏障時,屏障纔會開門,全部被屏障攔截的線程纔會繼續運行。

CyclicBarrier默認構造放時CyclicBarrier(int parities) ,其參數表示屏障攔截的線程數量,每一個線程調用await方法告訴CyclicBarrier我已經到達屏障,而後當前線程被阻塞。

87.CountDownLatch與CyclicBarrier區別

CountDownLatch:

計數器:計數器只能使用一次。

等待: 一個線程或多個等待另外n個線程完成以後才能執行。

CyclicBarrier:

計數器:計數器能夠重置(經過reset()方法)。

等待: n個線程相互等待,任何一個線程完成以前,全部的線程都必須等待。

88.Semaphore

用來控制同時訪問資源的線程數量,經過協調各個線程,來保證合理的公共資源的訪問。

應用場景:流量控制,特別是公共資源有限的應用場景,好比數據連接,限流等。

89.Exchanger

Exchanger是一個用於線程間協做的工具類,它提供一個同步點,在這個同步點上,兩個線程能夠交換彼此的數據。好比第一個線程執行exchange()方法,它會一直等待第二個線程也執行exchange,當兩個線程都到同步點,就能夠交換數據了。

通常來講爲了不一直等待的狀況,可使用exchange(V x,long timeout,TimeUnit unit),設置最大等待時間。

Exchanger能夠用於遺傳算法。

90.爲何使用線程池

幾乎全部須要異步或者併發執行任務的程序均可以使用線程池。合理使用會給咱們帶來如下好處。

  • 下降系統消耗:重複利用已經建立的線程下降線程建立和銷燬形成的資源消耗。
  • 提升響應速度: 當任務到達時,任務不須要等到線程建立就能夠當即執行。
  • 提供線程能夠管理性: 能夠經過設置合理分配、調優、監控。

91.線程池工做流程

一、判斷核心線程池裏的線程是否都有在執行任務,否->建立一個新工做線程來執行任務。是->走下個流程。

二、判斷工做隊列是否已滿,否->新任務存儲在這個工做隊列裏,是->走下個流程。

三、判斷線程池裏的線程是否都在工做狀態,否->建立一個新的工做線程來執行任務,

是->走下個流程。

四、按照設置的策略來處理沒法執行的任務。

92.建立線程池參數有哪些,做用?

public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 複製代碼

1.corePoolSize:核心線程池大小,當提交一個任務時,線程池會建立一個線程來執行任務,即便其餘空閒的核心線程可以執行新任務也會建立,等待須要執行的任務數大於線程核心大小就不會繼續建立。

2.maximumPoolSize:線程池最大數,容許建立的最大線程數,若是隊列滿了,而且已經建立的線程數小於最大線程數,則會建立新的線程執行任務。若是是無界隊列,這個參數基本沒用。

3.keepAliveTime: 線程保持活動時間,線程池工做線程空閒後,保持存活的時間,因此若是任務不少,而且每一個任務執行時間較短,能夠調大時間,提升線程利用率。

4.unit: 線程保持活動時間單位,天(DAYS)、小時(HOURS)、分鐘(MINUTES、毫秒MILLISECONDS)、微秒(MICROSECONDS)、納秒(NANOSECONDS)

5.workQueue: 任務隊列,保存等待執行的任務的阻塞隊列。

通常來講能夠選擇以下阻塞隊列:

ArrayBlockingQueue:基於數組的有界阻塞隊列。

LinkedBlockingQueue:基於鏈表的阻塞隊列。

SynchronizedQueue:一個不存儲元素的阻塞隊列。

PriorityBlockingQueue:一個具備優先級的阻塞隊列。

6.threadFactory:設置建立線程的工廠,能夠經過線程工廠給每一個建立出來的線程設置更有意義的名字。

  1. handler: 飽和策略也叫拒絕策略。當隊列和線程池都滿了,即達到飽和狀態。因此須要採起策略來處理新的任務。默認策略是AbortPolicy。

    AbortPolicy:直接拋出異常。

    CallerRunsPolicy: 調用者所在的線程來運行任務。

    DiscardOldestPolicy:丟棄隊列裏最近的一個任務,並執行當前任務。

    DiscardPolicy:不處理,直接丟掉。

    固然能夠根據本身的應用場景,實現RejectedExecutionHandler接口自定義策略。

93.向線程池提交任務

可使用execute()和submit() 兩種方式提交任務。

execute():無返回值,因此沒法判斷任務是否被執行成功。

submit():用於提交須要有返回值的任務。線程池返回一個future類型的對象,經過這個future對象能夠判斷任務是否執行成功,而且能夠經過future的get()來獲取返回值,get()方法會阻塞當前線程知道任務完成。get(long timeout,TimeUnit unit)能夠設置超市時間。

94.關閉線程池

能夠經過shutdown()或shutdownNow()來關閉線程池。它們的原理是遍歷線程池中的工做線程,而後逐個調用線程的interrupt來中斷線程,因此沒法響應終端的任務能夠能永遠沒法中止。

shutdownNow首先將線程池狀態設置成STOP,而後嘗試中止全部的正在執行或者暫停的線程,並返回等待執行任務的列表。

shutdown只是將線程池的狀態設置成shutdown狀態,而後中斷全部沒有正在執行任務的線程。

只要調用二者之一,isShutdown就會返回true,當全部任務都已關閉,isTerminaed就會返回true。

通常來講調用shutdown方法來關閉線程池,若是任務不必定要執行完,能夠直接調用shutdownNow方法。

95.線程池如何合理設置

配置線程池能夠從如下幾個方面考慮。

  • 任務是cpu密集型、IO密集型或者混合型

  • 任務優先級,高中低。

  • 任務時間執行長短。

  • 任務依賴性:是否依賴其餘系統資源。

    cpu密集型能夠配置可能小的線程,好比 n + 1個線程。

    io密集型能夠配置較多的線程,如 2n個線程。

    混合型能夠拆成io密集型任務和cpu密集型任務,

    若是兩個任務執行時間相差大,否->分解後執行吞吐量將高於串行執行吞吐量。

    否->不必分解。

    能夠經過Runtime.getRuntime().availableProcessors()來獲取cpu個數。

    建議使用有界隊列,增長系統的預警能力和穩定性。

96.Executor

從JDK5開始,把工做單元和執行機制分開。工做單元包括Runnable和Callable,而執行機制由Executor框架提供。

97.Executor框架的主要成員

ThreadPoolExecutor :能夠經過工廠類Executors來建立。

能夠建立3種類型的ThreadPoolExecutor:SingleThreadExecutor、FixedThreadPool、CachedThreadPool。

ScheduledThreadPoolExecutor :能夠經過工廠類Executors來建立。

能夠建立2中類型的ScheduledThreadPoolExecutor:ScheduledThreadPoolExecutor、SingleThreadScheduledExecutor

Future接口:Future和實現Future接口的FutureTask類來表示異步計算的結果。

Runnable和Callable:它們的接口實現類均可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor執行。Runnable不能返回結果,Callable能夠返回結果。

98.FixedThreadPool

可重用固定線程數的線程池。

查看源碼:

public static ExecutorService newFixedThreadPool(int nThreads) {
   return new ThreadPoolExecutor(nThreads, nThreads,
                                 0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>());}
複製代碼

corePoolSize 和maxPoolSize都被設置成咱們設置的nThreads。

當線程池中的線程數大於corePoolSize ,keepAliveTime爲多餘的空閒線程等待新任務的最長時間,超過這個時間後多餘的線程將被終止,若是設爲0,表示多餘的空閒線程會當即終止。

工做流程:

1.當前線程少於corePoolSize,建立新線程執行任務。

2.當前運行線程等於corePoolSize,將任務加入LinkedBlockingQueue。

3.線程執行完1中的任務,會循環反覆從LinkedBlockingQueue獲取任務來執行。

LinkedBlockingQueue做爲線程池工做隊列(默認容量Integer.MAX_VALUE)。所以可能會形成以下贏下。

1.當線程數等於corePoolSize時,新任務將在隊列中等待,由於線程池中的線程不會超過corePoolSize。

2.maxnumPoolSize等於說是一個無效參數。

3.keepAliveTime等於說也是一個無效參數。

4.運行中的FixedThreadPool(未執行shundown或shundownNow))則不會調用拒絕策略。

5.因爲任務能夠不停的加到隊列,當任務愈來愈多時很容易形成OOM。

99.SingleThreadExecutor

是使用單個worker線程的Executor。

查看源碼:

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
複製代碼

corePoolSize和maxnumPoolSize被設置爲1。其餘參數和FixedThreadPool相同。

執行流程以及形成的影響同FixedThreadPool.

100.CachedThreadPool

根據須要建立新線程的線程池。

查看源碼:

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
複製代碼

corePoolSize設置爲0,maxmumPoolSize爲Integer.MAX_VALUE。keepAliveTime爲60秒。

工做流程:

1.首先執行SynchronousQueue.offer (Runnable task)。若是當前maximumPool 中有空閒線程正在執行S ynchronousQueue.poll(keepAliveTIme,TimeUnit.NANOSECONDS),那麼主線程執行offer操做與空閒線程執行的poll操做配對成功,主線程把任務交給空閒線程執行,execute方 法執行完成;不然執行下面的步驟2。

  1. 當初始maximumPool爲空或者maximumPool中當前沒有空閒線程時,將沒有線程執行 SynchronousQueue.poll (keepAliveTime,TimeUnit.NANOSECONDS)。這種狀況下,步驟 1將失 敗。此時CachedThreadPool會建立一個新線程執行任務,execute()方法執行完成。

3.在步驟2中新建立的線程將任務執行完後,會執行SynchronousQueue.poll (keepAliveTime,TimeUnit.NANOSECONDS)。這個poll操做會讓空閒線程最多在SynchronousQueue中等待60秒鐘。若是60秒鐘內主線程提交了一個新任務(主線程執行步驟1),那麼這個空閒線程將執行主線程提交的新任務;不然,這個空閒線程將終止。因爲空閒60秒的空閒線程會被終止,所以長時間保持空閒的CachedThreadPool不會使用任何資源。

通常來講它適合處理時間短、大量的任務。

參考:

  • 《Java多線程編程核心技術》

  • 《Java高併發編程詳解》

  • 《Java 併發編程的藝術》

    博主整理 + 原創 15萬字面試題,包括17個專題。關注「Java小咖秀」回覆「面試」便可得到Java小咖秀面試筆記.pdf

    WechatIMG360
相關文章
相關標籤/搜索