Java併發知識總結

1、什麼是進程、線程?

  • 進程:進程是系統分配資源的最小單位,電腦中運行的一個程序就是一個進程,比如QQ打開後,就會有一個進程
  • 線程:線程是比進程更小的單位,是CPU調度的最小的單位,在一個進程中可以劃分多個進程,這些進程,共享進程的堆區和方法區的共享資源,但他們都有各自的虛擬機棧,程序計數器,本地方法棧,這些線程之間的切換比進程之間的切換快很多,所以線程也叫輕量級的進程。

2、什麼是線程安全和線程不安全?

  • 當有多個線程同時訪問一個共享資源時就會出現線程安全的問題,如果同一時間只有一個線程能得到共享資源就是線程安全的,也就是其他的線程的執行不會讓當前線程產生錯誤,比如有一個共享變量a初始值爲0,有1000個線程對a進行加一操作,如果不加保護的話,結果可能會小於1000,這就是線程不安全的。

3、什麼是自旋鎖?

  • 當多個線程訪問臨界區是,線程這有拿到了監視器鎖之後才能執行臨界區的代碼,但同一時間只有一個線程拿到鎖,沒到到鎖的線程只有變爲阻塞狀態,自旋鎖認爲線程等待的時間是非常短的,所以沒拿到所得時候,就執行一個循環等待,直到拿到鎖,這樣線程就不會進入阻塞狀態,但自旋的缺點就是,在自選的過程中會消耗CPU資源,造成浪費,也有自適應的自旋鎖,可以根據不同的情況來調整自旋的時間。

4、什麼是Java內存模型?

  • Java內存模型試圖屏蔽不同硬件不同操作系統內存訪問的差異,以實現java在不同平臺訪問內存都達到同樣的效果
  • CPU處理的速度和內存的速度是相差很大的,這樣CPU就會受到內存速度的限制導致CPU利用率不高,爲了解決這個問題就在CPU中加入了緩存,所以每個線程不僅有共享的主存還有自己的緩存,緩存中存放的是主存的副本,CPU操作的是cache中的副本,在適當的時間把cache中的數據寫會主存。

5、什麼是CAS?

  • CAS是指compare and swap,意識是指一箇舊的預期值A,主內存的值是B,要修改的值C,當且僅當A==B的時候,A的值纔會被修改成C,而且這個操作是原子性的,是一個非阻塞性的 樂觀鎖,比如在主存中有一個變量a = 1,線程的工作內存中有一個變量a的副本,現在線程要把a變成2,CAS就會用native本地方法比較工作內存中的值和主存中的值是否相等,如果相等則更新,防止了其他線程對數據的修改,當前線程修改了a後,其他線程工作內存中的緩存就會失效,會從新從主存中獲取。

6、什麼是樂觀鎖和悲觀鎖?

  • 樂觀鎖:對數據持一種樂觀的態度,認爲在併發過程中不會有別的線程修改當前線程用的數據,等到了真的用到數據的時候在檢查數據有沒有被別的線程修改過,比如CAS就是樂觀鎖
  • 悲觀鎖:對數據持一種悲觀的態度,認爲別的線程會修改當前線程用的數據,所以線程會加鎖,用完了在解鎖,比如容synchronize

7、什麼是AQS?

8、什麼是原子操作?在Java Concurrency API中有哪些原子類(atomic classes)?

  • 原子在化學中是不可再分的,一個原子操作通常包含幾個操作,這幾個操作都成功了這個原子操作纔算成功,如果有一個失敗了,其他的操作也會失敗。

1.基本類型

  • AtomicInteger:整形原子類
  • AtomicLong:長整型原子類
  • AtomicBoolean:布爾型原子類

2.數組類型

  • AtomicIntegerArray:整形數組原子類
  • AtomicLongArray:長整形數組原子類
  • AtomicReferenceArray:引用類型數組原子類

3.引用類型

  • AtomicReference:引用類型原子類
  • AtomicStampedRerence:原子更新引用類型裏的字段原子
  • AtomicMarkableReference :原子更新帶有標記位的引用類型

4.對象的屬性修改類型

  • AtomicIntegerFieldUpdater:原子更新整形字段的更新器
  • AtomicLongFieldUpdater:原子更新長整形字段的更新器
  • AtomicStampedReference:原子更新帶有版本號的引用類型。該類將整數值與引用關聯起來,可用於解決原子的更新數據和數據的版本號,可以解決使用CAS 進行原子更新時可能出現的 ABA 問題。

9、什麼是Executors框架?

10、什麼是阻塞隊列?如何使用阻塞隊列來實現生產者-消費者模型?

   BlockingQueue有兩種實現方式

  1. FIFO隊列:LinkBlockingQueue,ArrayBlockingQueue(固定長度)
  2. 優先級隊列:PriorityBlockingQueue

BlockingQueue提供了take()和put()方法,當隊列中是空的時候,take會阻塞知道隊列中有東西,當隊列滿了以後,put()方法會阻塞,等到隊列有位置後纔可以入隊

   BlockingQueue 實現生產者消費者問題

public class ProducerConsumer {

    private static BlockingQueue<String> queue = new ArrayBlockingQueue<>(5);

    private static class Producer extends Thread {
        @Override
        public void run() {
            try {
                queue.put("product");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.print("produce..");
        }
    }

    private static class Consumer extends Thread {

        @Override
        public void run() {
            try {
                String product = queue.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.print("consume..");
        }
    }
}

public static void main(String[] args) {
    for (int i = 0; i < 2; i++) {
        Producer producer = new Producer();
        producer.start();
    }
    for (int i = 0; i < 5; i++) {
        Consumer consumer = new Consumer();
        consumer.start();
    }
    for (int i = 0; i < 3; i++) {
        Producer producer = new Producer();
        producer.start();
    }
}

produce..produce..consume..consume..produce..consume..produce..consume..produce..consume..

11、什麼是Callable和Future?

  • 在jdk1.5之前,如果需要獲取子線程執行結果,就必須通過共享變量或者使用線程通信的方式來達到效果,這樣使用起來就比較麻煩,在1.5之後提供了Callable和Future接口,用他們可以方便的獲得線程的返回值,首先創建一個實現了Callable接口的類,然後創建一個FutureTask,把Callable當做參數,然後創建一個Thread來驅動FutureTask,可以用FutureTask的get方法獲取返回值

12、什麼是FutureTask?

  • FutureTask實現了RunableFuture接口,RunableFuture接口有繼承了Future可Runable接口,通過FutureTask可以獲得Callable的返回值

13、什麼是同步容器和併發容器的實現?

  • 同步容器:同步容器可以簡單的理解爲加了synchronize鎖的容器,比如Vector,Hashtable,以及Collections.synchronizedSet,synchronizedList等方法返回的容器。這些容器爲了線程安全都加了synchronize鎖,但這些容器幾乎沒有用,因爲在併發中效率太低了
  • 併發容器:併發容器採用了一種顆粒更細的加鎖方式,可以稱爲分段加鎖,比如ConcurrentHashMap,在這種鎖機制下,允許任意數量的讀線程併發地訪問map,並且執行讀操作的線程和寫操作的線程也可以併發的訪問map,同時允許一定數量的寫操作線程併發地修改map,所以它可以在併發環境下實現更高的吞吐量。

14、什麼是多線程?優缺點?

多線程就是多個線程併發執行,一個進程要完成一個任務,把這個任務劃分成更小的任務分給每一個線程,這幾個線程併發執行,就是多線程

優點:

  1. 從計算機底層來說:線程可以看作是輕量級的進程,是CPU調度的最小的單位,線程之間的切換要比進程之間切換快的多,另外,多核 CPU 時代意味着多個線程可以同時運行,這減少了線程上下文切換的開銷。
  2. 從當代的互聯網來說:現在的系統動不動就要求千萬級的併發,而多線程併發編程是併發系統的基礎,利用好多線程可以大大提高系統的併發量
  3. 可以提高CPU的利用率:在單核時代,要先進行IO然後再進行計算,這樣二者的利用率只有50%,多線程可以在CPU計算的時候進行IO操作這樣CPU的利用率就提高了,在多核時代,假如有一個任務,把這個任務分配給不同的線程,這些線程在不同的CPU中跑,可以讓每一個CPU充分利用。

缺點:

  • 併發編程的目的是提高程序的執行效率提高程序執行的速度,但也存在很多問題比如上下文之間的切換問題,死鎖問題,以及受限於軟件和硬件資源的問題。

15、什麼是多線程的上下文切換?

  • 併發執行的每個線程都會分一個時間片,當線程的執行時間到了,就會讓出CPU給別的線程執行,線程保存當前執行的狀態,到下一個線程執行的過程就是上下文切換。上下文切換是非常頻繁的,一秒鐘要切換上百次,每一次切換都是納秒級別的,所以上下文切換是非常消耗CPU的,有可能是操作系統消耗最大的操作,linux比其他系統有很多優點,其中一個就是上下文切換的速度很快。

16、ThreadLocal的設計理念與作用?

在併發編程中每一個線程都可以使用共享資源,所以我們要對這些共享資源做同步,但做同步會涉及到很多問題,所以爲什麼不讓每個線程都擁有這個共享資源的副本呢,那每個線程對副本進行修改,就互不干擾了,所以ThreadLocal就是解決這個問題的,ThreadLocal可以綁定每個線程的數據,當線程訪問該變量的時候就會拿到當前線程的變量。

  • 原理:在ThreadLocal類中有一個Map,Map以線程爲Key值,所以線程可以拿到當前線程綁定的數據。

  • 存在的問題:Map中的key值是threadlocald的弱引用,當沒有被外部強引用的時候會被清理,但value是強引用不會被清理,所以就存在Key爲null的值,這樣就會造成內存泄漏,但ThreadLocalMap已經解決了這個問題,在每次get()和set()的時候都會清理key爲null的值

  • 應用舉例:在數據庫額連接池中要保證每個線程都擁有自己的connection,不然事務就會出現混亂,所以就可以把每個線程的connection綁定到當前前程中去,就可以保證同一個線程用同一個連接

17、ThreadPool(線程池)用法與優勢?

  1. 降低資源消耗。 通過重複利用已創建的線程降低線程創建和銷燬造成的消耗。
  2. 提高響應速度。 當任務到達時,任務可以不需要的等到線程創建就能立即執行。
  3. 提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。

18、Concurrent包裏的其他東西

  • ArrayBlockingQueue、CountDownLatch等等。

19、synchronized和ReentrantLock的區別?

  1. 都是可重入鎖
  2. synchronized是基於jvm層面的ReentrantLock是基於jdk層面的
  3. ReentrantLock比synchronized的功能更多:①等待可中斷;②可實現公平鎖;③可實現選擇性通知(鎖可以綁定多個條件)

20、Semaphore有什麼作用?

Semaphore 類似於操作系統中的信號量,可以控制對互斥資源的訪問線程數。

public class SemaphoreExample {

    public static void main(String[] args) {
        final int clientCount = 3;
        final int totalRequestCount = 10;
        Semaphore semaphore = new Semaphore(clientCount);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < totalRequestCount; i++) {
            executorService.execute(()->{
                try {
                    semaphore.acquire();
                    System.out.print(semaphore.availablePermits() + " ");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            });
        }
        executorService.shutdown();
    }
}
before..before..before..before..before..before..before..before..before..before..after..after..after..after..after..after..after..after..after..after..

21、Java Concurrency API中的Lock接口(Lock interface)是什麼?對比同步它有什麼優勢?

Lock接口比同步方法和同步塊提供了更具擴展性的鎖操作。
他們允許更靈活的結構,可以具有完全不同的性質,並且可以支持多個相關類的條件對象。
它的優勢有

  1. 可以使鎖更公平
  2. 可以使線程在等待鎖的時候響應中斷
  3. 可以讓線程嘗試獲取鎖,並在無法獲取鎖的時候立即返回或者等待一段時間 可以在不同的範圍,以不同的順序獲取和釋放鎖

整體上來說Lock是synchronized的擴展版,Lock提供了無條件的、可輪詢的(tryLock方法)、定時的(tryLock帶參方法)、可中斷的(lockInterruptibly)、可多條件隊列的(newCondition方法)鎖操作。另外Lock的實現類基本都支持非公平鎖(默認)和公平鎖,synchronized只支持非公平鎖,當然,在大部分情況下,非公平鎖是高效的選擇。

22、Hashtable的size()方法中明明只有一條語句”return count”,爲什麼還要做同步?

23、ConcurrentHashMap的併發度是什麼?

24、ReentrantReadWriteLock讀寫鎖的使用?

25、CyclicBarrier和CountDownLatch的用法及區別?

26、LockSupport工具?

27、Condition接口及其實現原理?

28、Fork/Join框架的理解?

29、wait()和sleep()的區別?

  • wait()和sleep()都可以暫定現在的進程,但是wait()會釋放當前的鎖,但sleep不會釋放鎖,sleep在時間到了以後會自己甦醒,繼續執行,而wait需要別的線程調用他的notify()或者notifyAll()方法。

30、線程的五個狀態(五種狀態,創建、就緒、運行、阻塞和死亡)?

在這裏插入圖片描述

  • 當線程創建完成以後就是New(新建)狀態,當調用star()之後就成爲Runnable可運行狀態,可運行狀態其實包含就緒狀態和運行狀態,當線程得到時間片後就成爲運狀態,當線程沒有獲得排它鎖的時候就會變成阻塞狀態,調用了wait(),方法後就會成爲waiting狀態。

31、start()方法和run()方法的區別?

當創建了一個線程後,線程就會進入新建狀態,當調用star()後就會進入就緒狀態,一旦獲得時間片後就會進入運行狀態,會自動的執行線程中的run()方法,調用start()方法會啓動一個線程,和當前線程是異步的,而調用run()方法只是調用了線程中的一個普通方法,和調用普通類的方法沒什麼區別,沒有啓動線程,和當前的線程是同步執行的。

32、Runnable接口和Callable接口的區別?

實現了Runnable和Callable接口的類只是一個任務,並不能算是一個線程,需要通過Thread來調用,可以說任務是通過線程驅動而執行的。二者的區別就是Callable接口可以有返回值,返回值通過 FutureTask 進行封裝。

public class MyCallable implements Callable<Integer> {
    public Integer call() {
        return 123;
    }
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
    MyCallable mc = new MyCallable();
    FutureTask<Integer> ft = new FutureTask<>(mc);
    Thread thread = new Thread(ft);
    thread.start();
    System.out.println(ft.get());
}

33、volatile關鍵字的作用?

volatile關鍵字可以實現多線程之間數據的可見性,但並不能保證在多線程下是線程安全的,而且可以防止指令重排

原理:

  • 由於CPU和內存之間速度相差很大,這樣會導致CPU的利用率不高,所以在CPU中加了緩存,這樣就可以解決這個問題,java內存模型中每次不是從主存中取數據,而是從每個線程自己的工作內存中取數據,但是這樣就會造成一個問題就是在多線程中會出現緩存不一致的問題,比如有一個變量a在主存中的初始值是1,每個線程的工作內存中都有一個a的副本,當一個線程修改了a的值,這時候a變成了0,但是其他線程並不知道,其他線程自己的工作內存中還是原來的舊值,當加了volatile關鍵字後,就要遵循操作系統的緩存一致協議,當前線程修改了之後,會被強制刷回主存,每一個線程就會在總線上嗅探是否有值改變了,如果發現主存中的值變了,那工作內存中的值就會失效,也就是,當前線程對值的修改對其他線程是可見的。
  • 雖然可以實現數據的可見性,但並不能保證原子性,所以在多線程下並不能保證是線程安全的

34、Java中如何獲取到線程dump文件?

35、線程和進程有什麼區別?

  1. 線程是資源分配的最小單位,進程是CPU調度的最小單位
  2. 線程之間的切換比進程之間的切換快很多,線程可以看成是輕量級的進程
  3. 進程之間是獨立的,線程之間是相互聯繫的,線程之間共享進程的共享資源

36、線程實現的方式有幾種(四種)?

  1. 繼承Thread類
  2. 實現Runnable接口
  3. 實現Callable接口
  4. 4.使用線程池(有返回值

37、高併發、任務執行時間短的業務怎樣使用線程池?併發不高、任務執行時間長的業務怎樣使用線程池?併發高、業務執行時間長的業務怎樣使用線程池?

38、如果你提交任務時,線程池隊列已滿,這時會發生什麼?

39、鎖的等級:方法鎖、對象鎖、類鎖?

40、如果同步塊內的線程拋出異常會發生什麼?

41、併發編程(concurrency)並行編程(parallellism)有什麼區別?

42、如何保證多線程下 i++ 結果正確?

1. 可以用synchronize關鍵字
2. 用CAS+volatile

43、一個線程如果出現了運行時異常會怎麼樣?

44、如何在兩個線程之間共享數據?

45、生產者消費者模型的作用是什麼?

46、怎麼喚醒一個阻塞的線程?

47、Java中用到的線程調度算法是什麼

48、單例模式的線程安全性?

public class Singleton {

    private volatile static Singleton uniqueInstance;

    private Singleton() {
    }

    public static Singleton getUniqueInstance() {
       //先判斷對象是否已經實例過,沒有實例化過才進入加鎖代碼
        if (uniqueInstance == null) {
            //類對象加鎖
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

49、線程類的構造方法、靜態塊是被哪個線程調用的?

50、同步方法和同步塊,哪個是更好的選擇?

  • 應該儘量使用同步塊而不是同步方法,這樣可以縮小同步範圍,從而減少鎖爭用

51、如何檢測死鎖?怎麼預防死鎖?

  死鎖滿足的條件

  1. 互斥資源
  2. 不可剝奪
  3. 請求保持
  4. 環路等待
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章