java多線程并發(fā)的筆試、面試必備知識(shí)大全,不看就要被淘汰了

關(guān)注我,可以獲取最新知識(shí)、經(jīng)典面試題以及技術(shù)分享

??多線程和并發(fā)是求職大小廠面試中必問(wèn)的知識(shí)點(diǎn),其涉及到點(diǎn)很多,難度很大。有些人面對(duì)這些問(wèn)題有點(diǎn)迷茫,為了解決這情況,總結(jié)了一下java多線程并發(fā)的基礎(chǔ)知識(shí)點(diǎn)。而且要想深入研究java多線程并發(fā)也必須先掌握基礎(chǔ)知識(shí),可為后續(xù)各個(gè)模塊深入研究做好做好準(zhǔn)備。現(xiàn)在廢話不多說(shuō),各位看官請(qǐng)查看基礎(chǔ)知識(shí)點(diǎn),后續(xù)還有源碼解析(synchronize底層原理,線程池原理,LockAQS,同步、并發(fā)容器等源碼解析)。

1 基本概念

? 程序: 是計(jì)算機(jī)指令的集合,它以文件的形式存儲(chǔ)在磁盤上,即程序是靜態(tài)的代碼

? 進(jìn)程:

  • 是一個(gè)程序在其自身的地址空間中的一次執(zhí)行活動(dòng),是系統(tǒng)運(yùn)行程序的基本單位
  • 進(jìn)程是資源申請(qǐng)、調(diào)度和獨(dú)立運(yùn)行的單位

? 線程:

  • 是進(jìn)程中的一個(gè)單一的連續(xù)控制流程。一個(gè)進(jìn)程可以擁有多個(gè)線程。
  • 線程又稱為輕量級(jí)進(jìn)程,它和進(jìn)程一樣擁有獨(dú)立的執(zhí)行控制,由操作系統(tǒng)負(fù)責(zé)調(diào)度,區(qū)
    別在于線程沒(méi)有獨(dú)立的存儲(chǔ)空間,而是和所屬進(jìn)程中的其它線程共享一個(gè)存儲(chǔ)空間,這
    使得線程間的通信遠(yuǎn)較進(jìn)程簡(jiǎn)單。

? 三者之間的關(guān)系:

  • 線程是進(jìn)程劃分成的更小的運(yùn)行單位。線程和進(jìn)程最大的不同在于基本上各進(jìn)程是獨(dú)立的,而各線程則不一定,因?yàn)橥贿M(jìn)程中的線程極有可能會(huì)相互影響。
  • 從另一角度來(lái)說(shuō),進(jìn)程屬于操作系統(tǒng)的范疇,主要是同一段時(shí)間內(nèi),可以同時(shí)執(zhí)行一個(gè)以上的程序,而線程則是在同一程序內(nèi)幾乎同時(shí)執(zhí)行一個(gè)以上的程序段。
image

內(nèi)存機(jī)制可查看文章《推薦收藏系列:一文理解JVM虛擬機(jī)(內(nèi)存、垃圾回收、性能優(yōu)化)解決面試中遇到問(wèn)題》

2 線程組成

組成部分:虛擬CPU、執(zhí)行的代碼以及處理的數(shù)據(jù)。


image

3 線程與進(jìn)程區(qū)別

? 進(jìn)程: 指系統(tǒng)中正在運(yùn)行中的應(yīng)用程序,它擁有自己獨(dú)立的內(nèi)存空間;

? 線程: 是指進(jìn)程中一個(gè)執(zhí)行流程,一個(gè)進(jìn)程中允許同時(shí)啟動(dòng)多個(gè)線程,他們分別執(zhí)行不同的任務(wù),多個(gè)線程共享內(nèi)存,從而極大地提高了程序的運(yùn)行效率;

? 主要區(qū)別:

  • 每個(gè)進(jìn)程都需要操作系統(tǒng)為其分配獨(dú)立的內(nèi)存地址空間
  • 而同一進(jìn)程中的所有線程在同一塊地址空間中,這些線程可以共享數(shù)
    據(jù),因此線程間的通信比較簡(jiǎn)單,消耗的系統(tǒng)開(kāi)銷也相對(duì)較小

4 為什么要使用多線程

? 使用多線程好處:

  • 可以同時(shí)并發(fā)執(zhí)行多個(gè)任務(wù)
  • 程序的某個(gè)功能部分正在等待某些資源的時(shí)候,此時(shí)又不愿意因?yàn)榈却斐沙绦驎和?,那么就可以?chuàng)建另外的線程進(jìn)行其它的工作;
  • 多線程可以最大限度地減低CPU的閑置時(shí)間,從而提高CPU的利用率;

5 主線程

Java程序啟動(dòng)時(shí),一個(gè)線程立刻運(yùn)行,它執(zhí)行main方法,這個(gè)線程稱為程序的主線程,任何Java程序都至少有一個(gè)線程,即主線程。

? 主線程的特殊之處在于:

  • 它是產(chǎn)生其它線程子線程的線程;
  • 通常它必須最后結(jié)束,因?yàn)樗獔?zhí)行其它子線程的關(guān)閉工作。

6 線程優(yōu)先級(jí)

單核計(jì)算機(jī)只有一個(gè)CPU,各個(gè)線程輪流獲得CPU的使用權(quán),才能執(zhí)行任務(wù):

  • 優(yōu)先級(jí)較高的線程有更多獲得CPU的機(jī)會(huì),反之亦然;
  • 優(yōu)先級(jí)用整數(shù)表示,取值范圍是1~10,一般情況下,線程的默認(rèn)
  • 優(yōu)先級(jí)都是5,但是也可以通過(guò)setPriority和getPriority方法來(lái)設(shè)置或返回優(yōu)先級(jí);

? Thread類有如下3個(gè)靜態(tài)常量來(lái)表示優(yōu)先級(jí):

  • MAX_PRIORITY:取值為10,表示最高優(yōu)先級(jí)
  • MIN_PRIORITY:取值為1,表示最低優(yōu)先級(jí)
  • NORM_PRIORITY:取值為5,表示默認(rèn)的優(yōu)先級(jí)

7 線程的生命周期

image

? 線程狀態(tài)(State枚舉值代表線程狀態(tài)):

  • 新建狀態(tài)( NEW): 線程剛創(chuàng)建, 尚未啟動(dòng)。Thread thread = new Thread()
  • 可運(yùn)行狀態(tài)(RUNNABLE): 線程對(duì)象創(chuàng)建后,其他線程(比如 main 線程)調(diào)用了該對(duì)象的 start 方法。該狀態(tài)的線程位于可運(yùn)行線程池中,等待被線程調(diào)度選中,獲取 cpu 的使用權(quán)。
  • 運(yùn)行(running): 線程獲得 CPU 資源正在執(zhí)行任務(wù)(run() 方法),此時(shí)除非此線程自動(dòng)放棄 CPU 資源或者有優(yōu)先級(jí)更高的線程進(jìn)入,線程將一直運(yùn)行到結(jié)束
  • 阻塞狀態(tài)(Blocked): 線程正在運(yùn)行的時(shí)候,被暫停,通常是為了等待某個(gè)時(shí)間的發(fā)生(比如說(shuō)某項(xiàng)資源就緒)之后再繼續(xù)運(yùn)行。sleep,suspend,wait等方法都可以導(dǎo)致線程阻塞
  • 等待(WAITING): 進(jìn)入該狀態(tài)的線程需要等待其他線程做出一些特定動(dòng)作(通知或中斷)。
  • 超時(shí)等待(TIMED_WAITING): 該狀態(tài)不同于WAITING,它可以在指定的時(shí)間后自行返回。
  • 終止(TERMINATED): 表示該線程已經(jīng)執(zhí)行完畢,如果一個(gè)線程的run方法執(zhí)行結(jié)束或者調(diào)用stop方法后,該線程就會(huì)死亡。對(duì)于已經(jīng)死亡的線程,無(wú)法再使用start方法令其進(jìn)入就緒。

? 線程在Running的過(guò)程中可能會(huì)遇到阻塞(Blocked)情況:

  • 調(diào)用join()sleep()方法,sleep()時(shí)間結(jié)束或被打斷,join()中斷,IO完成都會(huì)回到Runnable狀態(tài),等待JVM的調(diào)度。
  • 調(diào)用wait(),使該線程處于等待池(wait blocked pool),直到notify()/notifyAll(),線程被喚醒被放到鎖定池(lock blocked pool ),釋放同步鎖使線程回到可運(yùn)行狀態(tài)(Runnable)
  • 對(duì)Running狀態(tài)的線程加同步鎖(Synchronized)使其進(jìn)入(lock blocked pool ),同步鎖被釋放進(jìn)入可運(yùn)行狀態(tài)(Runnable)。

8 線程創(chuàng)建方式

? 線程創(chuàng)建方式:

  • 實(shí)現(xiàn)Runnable接口,重載run(),無(wú)返回值
  • 繼承Thread類,復(fù)寫run()
  • 實(shí)現(xiàn)Callable接口,通過(guò)FutureTask/Future來(lái)創(chuàng)建有返回值的Thread線程,通過(guò)Executor執(zhí)行
  • 使用Executors創(chuàng)建ExecutorService,入?yún)allable或Future

1.實(shí)現(xiàn)Runnable接口,重載run(),無(wú)返回值,Runnable接口的存在主要是為了解決Java中不允許多繼承的問(wèn)題。

public class ThreadRunnable implements Runnable {
  public void run() {
    for (int i = 0; i < 10; i++) {
      System.out.println(Thread.currentThread().getName() + ":" + i);
    }
  }
}
  
public class ThreadMain {
  public static void main(String[] args) throws Exception {
    ThreadRunnable threadRunnable1 = new ThreadRunnable();
    ThreadRunnable threadRunnable2 = new ThreadRunnable();
    ThreadRunnable threadRunnable3 = new ThreadRunnable();
    Thread thread1 = new Thread(threadRunnable1);
    Thread thread2 = new Thread(threadRunnable2);
    Thread thread3 = new Thread(threadRunnable3);    
    thread1.start();
    thread2.start();
    thread3.start();
  }
}

2.繼承Thread類,重寫run(),通過(guò)調(diào)用Thread的start()會(huì)調(diào)用創(chuàng)建線程的run(),不同線程的run方法里面的代碼交替執(zhí)行。但由于Java不支持多繼承.因此繼承Thread類就代表這個(gè)子類不能繼承其他類.

public class ThreadCustom extends Thread {
  public void run() {
    for (int i = 0; i < 10; i++) {
      System.out.println(Thread.currentThread() + ":" + i);
    }
  }
}
  
  
public class ThreadTest {
  public static void main(String[] args)
  {
    ThreadCustom thread = new ThreadCustom();
    thread.start();
  }
}

3.實(shí)現(xiàn)Callable接口,通過(guò)FutureTask/Future來(lái)創(chuàng)建有返回值的Thread線程,通過(guò)Executor執(zhí)行,該方式有返回值,可以獲得異步。

public class ThreadCallableCustom {
  public static void main(String[] args) throws Exception {
    FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>() {
      public Integer call() throws Exception {
        for (int i = 0; i < 10; i++) {
          System.out.println(Thread.currentThread().getName() + ":" + i);
        }
        return 1;
      }
    });
    Executor executor = Executors.newFixedThreadPool(1);
    ((ExecutorService) executor).submit(futureTask);
  
    //獲得線程執(zhí)行狀態(tài)
    System.out.println(Thread.currentThread().getName() + ":" + futureTask.get());
  }
}

4.使用Executors創(chuàng)建ExecutorService,入?yún)allable或Future,適用于線程池和并發(fā)

public class ThreadExecutors {
  private final String threadName;
  
  public ThreadExecutors(String threadName) {
    this.threadName = threadName;
  }
  
  private ThreadFactory createThread() {
    ThreadFactory tf = new ThreadFactory() {
      public Thread newThread(Runnable r) {
        Thread thread = new Thread();
        thread.setName(threadName);
        thread.setDaemon(true);
        try {
          sleep(1000);
        }
        catch (InterruptedException e) {
          e.printStackTrace();
        }
        return thread;
      }
    };
    return tf;
  }
  
  public Object runCallable(Callable callable) {
    return Executors.newSingleThreadExecutor(createThread()).submit(callable);
  }
  
  public Object runFunture(Runnable runnable) {
    return Executors.newSingleThreadExecutor(createThread()).submit(runnable);
  }
}

public class ThreadTest {
  public static void main(String[] args) throws Exception {
    ThreadExecutors threadExecutors = new ThreadExecutors("callableThread");
    threadExecutors.runCallable(new Callable() {
      public String call() throws Exception {
        return "success";
      }
    });
  
    threadExecutors.runFunture(new Runnable() {
      public void run() {
        System.out.println("execute runnable thread.");
      }
    });
  }
}

9 Runnable接口和Callable接口區(qū)別

1)兩個(gè)接口需要實(shí)現(xiàn)的方法名不一樣,Runnable需要實(shí)現(xiàn)的方法為run(),Callable需要實(shí)現(xiàn)的方法為call()。
2)實(shí)現(xiàn)的方法返回值不一樣,Runnable任務(wù)執(zhí)行后無(wú)返回值,Callable任務(wù)執(zhí)行后可以得到異步計(jì)算的結(jié)果。
3)拋出異常不一樣,Runnable不可以拋出異常,Callable可以拋出異常。

10 線程安全

?線程安全定義

當(dāng)多個(gè)線程訪問(wèn)某個(gè)一類(對(duì)象或方法)時(shí),這個(gè)類始終都能表現(xiàn)出正確的行為,那么這個(gè)類(對(duì)象或方法)就是線程安全的(即在多線程環(huán)境中被調(diào)用時(shí),能夠正確地處理多個(gè)線程之間的共享變量,使程序功能正確完成)。

?線程安全示例

餓漢式單例模式-線程安全

public class EagerSingleton(){
 
    private static final EagerSingleton instance = new EagerSingleton();
 
    private EagerSingleton(){};
    
    public static EagerSingleton getInstance(){
       return instance;
    }
}

?如何解決線程安全問(wèn)題?

可以通過(guò)加鎖的方式:

  • 同步(synchronized)代碼塊:只需要將操作共享數(shù)據(jù)的代碼放在synchronized
  • 同步(synchronized)方法:將操作共享數(shù)據(jù)的代碼抽取出來(lái)放到一個(gè)synchronized方法里面就可以了
  • Lock鎖:加同步鎖 lock() 以及釋放同步鎖unlock()

11 什么是死鎖、活鎖?

死鎖,是指兩個(gè)或兩個(gè)以上的進(jìn)程(或線程)在執(zhí)行過(guò)程中,因爭(zhēng)奪資源而造成的一種互相等待的現(xiàn)象,若無(wú)外力作用,它們都將無(wú)法推進(jìn)下去。

活鎖,任務(wù)或者執(zhí)行者沒(méi)有被阻塞,由于某些條件沒(méi)有滿足,導(dǎo)致一直重復(fù)嘗試,失敗,嘗試,失敗。

? 產(chǎn)生死鎖的必要條件:

  • 互斥條件:所謂互斥就是進(jìn)程在某一時(shí)間內(nèi)獨(dú)占資源。
  • 請(qǐng)求與保持條件:一個(gè)進(jìn)程因請(qǐng)求資源而阻塞時(shí),對(duì)已獲得的資源保持不放。
  • 不剝奪條件:進(jìn)程已獲得資源,在末使用完之前,不能強(qiáng)行剝奪。
  • 循環(huán)等待條件:若干進(jìn)程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系。

? 死鎖的解決方法:

  • 撤消陷于死鎖的全部進(jìn)程。
  • 逐個(gè)撤消陷于死鎖的進(jìn)程,直到死鎖不存在。
  • 從陷于死鎖的進(jìn)程中逐個(gè)強(qiáng)迫放棄所占用的資源,直至死鎖消失。
    從另外一些進(jìn)程那里強(qiáng)行剝奪足夠數(shù)量的資源分配給死鎖進(jìn)程,以解除死鎖狀態(tài)。

12 什么是悲觀鎖、樂(lè)觀鎖?

1)悲觀鎖

悲觀鎖,總是假設(shè)最壞的情況,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)阻塞直到它拿到鎖。

  • 傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。
  • Java 里面的同步原語(yǔ) synchronized 關(guān)鍵字的實(shí)現(xiàn)也是悲觀鎖。

2)樂(lè)觀鎖

樂(lè)觀鎖,顧名思義,就是很樂(lè)觀,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人不會(huì)修改,所以不會(huì)上鎖,但是在更新的時(shí)候會(huì)判斷一下在此期間別人有沒(méi)有去更新這個(gè)數(shù)據(jù),可以使用版本號(hào)等機(jī)制。樂(lè)觀鎖適用于多讀的應(yīng)用類型,這樣可以提高吞吐量。

13 多個(gè)線程間鎖的并發(fā)控制

多個(gè)線程間鎖的并發(fā)控制,對(duì)象鎖多個(gè)線程、每個(gè)線程持有該方法所屬對(duì)象的鎖以及類鎖。synchronized, wait, notify 是任何對(duì)象都具有的同步工具

?對(duì)象鎖的同步和異步

  • 同步:synchronized,同步的概念就是共享,只需要針對(duì)共享的資源,才需要考慮同步。
  • 異步:asynchronized,異步的概念就是獨(dú)立,相互之間不受到任何制約。

同步的目的就是為線程安全,其實(shí)對(duì)于線程安全來(lái)說(shuō),需要滿足兩個(gè)特性:原子性(同步)、可見(jiàn)性。

14 Volatile關(guān)鍵字

?Volatile作用,實(shí)現(xiàn)變量在多個(gè)線程間可見(jiàn),保證內(nèi)存可見(jiàn)性和禁止指令重排

多線程的內(nèi)存模型:main memory(主存)、working

memory(線程棧),在處理數(shù)據(jù)時(shí),線程會(huì)把值從主存load到本地棧,完成操作后再save回去(volatile關(guān)鍵詞的作用:每次針對(duì)該變量的操作都激發(fā)一次load and save)。

image

15 ThreadLocal

??線程局部變量,以空間換時(shí)間的手段,為每個(gè)線程提供變量的獨(dú)立副本,以無(wú)鎖的情況下保障線程安全。主要解決的就是讓每個(gè)線程執(zhí)行完成之后再結(jié)束,這個(gè)時(shí)候就要用到j(luò)oin()方法。

?適用場(chǎng)景:

  • 在并發(fā)不是很高的時(shí)候,加鎖的性能會(huì)更好
  • 在高并發(fā)量場(chǎng)景下,使用ThreadLocal可以在一定程度上減少鎖競(jìng)爭(zhēng)。

16 多線程同步和互斥實(shí)現(xiàn)方法

??1). 線程同步,是指線程之間所具有的一種制約關(guān)系,一個(gè)線程的執(zhí)行依賴另一個(gè)線程的消息,當(dāng)它沒(méi)有得到另一個(gè)線程的消息時(shí)應(yīng)等待,直到消息到達(dá)時(shí)才被喚醒。

? 線程間的同步方法,大體可分為兩類:用戶模式和內(nèi)核模式。顧名思義:

內(nèi)核模式,就是指利用系統(tǒng)內(nèi)核對(duì)象的單一性來(lái)進(jìn)行同步,使用時(shí)需要切換內(nèi)核態(tài)與用戶態(tài)。內(nèi)核模式下的方法有:

  • 事件
  • 信號(hào)量
  • 互斥量

用戶模式,就是不需要切換到內(nèi)核態(tài),只在用戶態(tài)完成操作。用戶模式下的方法有:

  • 原子操作(例如一個(gè)單一的全局變量)
  • 臨界區(qū)

??2). 線程互斥,是指對(duì)于共享的進(jìn)程系統(tǒng)資源,在各單個(gè)線程訪問(wèn)時(shí)的排它性。

當(dāng)有若干個(gè)線程都要使用某一共享資源時(shí),任何時(shí)刻最多只允許一個(gè)線程去使用,其它要使用該資源的線程必須等待,直到占用資源者釋放該資源。
線程互斥可以看成是一種特殊的線程同步。

17 線程之間通信

線程是操作系統(tǒng)中獨(dú)立的個(gè)體,但這些個(gè)體之間如果不經(jīng)過(guò)特殊的協(xié)作就不能成為一個(gè)整體,線程間的通信就成為整體的必用方式之一。

?線程間通信的幾種方式?

線程之間的通信方式:

  • 共享內(nèi)存
  • 消息傳遞

共享內(nèi)存:在共享內(nèi)存的并發(fā)模型里,線程之間共享程序的公共狀態(tài),線程之間通過(guò)寫-讀內(nèi)存中的公共狀態(tài)來(lái)隱式進(jìn)行通信。典型的共享內(nèi)存通信方式,就是通過(guò)共享對(duì)象進(jìn)行通信。

image

消息傳遞:在消息傳遞的并發(fā)模型里,線程之間沒(méi)有公共狀態(tài),線程之間必須通過(guò)明確的發(fā)送消息來(lái)顯式進(jìn)行通信。在 Java 中典型的消息傳遞方式,就是 wait()notify() ,或者 BlockingQueue 。

image

18 什么是 Java Lock 接口?

java.util.concurrent.locks.Lock 接口,比 synchronized 提供更具拓展行的鎖操作。它允許更靈活的結(jié)構(gòu),可以具有完全不同的性質(zhì),并且可以支持多個(gè)相關(guān)類的條件對(duì)象。它的優(yōu)勢(shì)有:

  • 可以使鎖更公平。
  • 可以使線程在等待鎖的時(shí)候響應(yīng)中斷。
  • 可以讓線程嘗試獲取鎖,并在無(wú)法獲取鎖的時(shí)候立即返回或者等待一段時(shí)間。
  • 可以在不同的范圍,以不同的順序獲取和釋放鎖。
image

19 Java AQS

AQS ,AbstractQueuedSynchronizer ,即隊(duì)列同步器。它是構(gòu)建鎖或者其他同步組件的基礎(chǔ)框架(如 ReentrantLock、ReentrantReadWriteLock、Semaphore 等),J.U.C 并發(fā)包的作者(Doug Lea)期望它能夠成為實(shí)現(xiàn)大部分同步需求的基礎(chǔ)。它是 J.U.C 并發(fā)包中的核心基礎(chǔ)組件。

? 優(yōu)勢(shì):

AQS 解決了在實(shí)現(xiàn)同步器時(shí)涉及當(dāng)?shù)拇罅考?xì)節(jié)問(wèn)題,例如獲取同步狀態(tài)、FIFO 同步隊(duì)列?;?AQS 來(lái)構(gòu)建同步器可以帶來(lái)很多好處。它不僅能夠極大地減少實(shí)現(xiàn)工作,而且也不必處理在多個(gè)位置上發(fā)生的競(jìng)爭(zhēng)問(wèn)題。

在基于 AQS 構(gòu)建的同步器中,只能在一個(gè)時(shí)刻發(fā)生阻塞,從而降低上下文切換的開(kāi)銷,提高了吞吐量。同時(shí)在設(shè)計(jì) AQS 時(shí)充分考慮了可伸縮性,因此 J.U.C 中,所有基于 AQS 構(gòu)建的同步器均可以獲得這個(gè)優(yōu)勢(shì)。

20 同步類容器

何為同步容器?可以簡(jiǎn)單地理解為通過(guò)synchronized來(lái)實(shí)現(xiàn)同步的容器,如果有多個(gè)線程調(diào)用同步容器的方法,它們將會(huì)串行執(zhí)行。

? 特點(diǎn):

  • 是線程安全的
  • 某些場(chǎng)景下可能需要加鎖來(lái)保護(hù)復(fù)合操作

? 常見(jiàn)同步類容器:

  • 如Vector、HashTable
  • 使用JDK的Collections.synchronized等工廠方法去創(chuàng)建實(shí)現(xiàn)的。
  • 底層是用傳統(tǒng)的synchronized關(guān)鍵字對(duì)方法進(jìn)行同步。
  • 無(wú)法滿足高并發(fā)場(chǎng)景下的性能需求

21 并發(fā)類容器

jdk5.0以后提供了多種并發(fā)類容器來(lái)替代同步類容器從而改善性能。

?同步類容器局限性:

  • 都是串行化的。
  • 雖實(shí)現(xiàn)了線程安全,卻降低了并發(fā)性
  • 在多線程環(huán)境時(shí),嚴(yán)重降低了應(yīng)用程序的吞吐量。

?常用的并發(fā)類容器:

  • ConcurrentHashMap
  • Copy-On-Write容器

? ConcurrentHashMap原理

  • 內(nèi)部使用段(Segment)來(lái)表示這些不同的部分
  • 每個(gè)段相當(dāng)于一個(gè)小的HashTable,它們有自己的鎖。
  • 把一個(gè)整體分成了16個(gè)段(Segment)。也就是最高支持16個(gè)線程的并發(fā)修改操作。
  • 這也是在多線程場(chǎng)景時(shí)通過(guò)減小鎖的粒度從而降低鎖競(jìng)爭(zhēng)的一種方案。

? Copy-On-Write容器

Copy-On-Write簡(jiǎn)稱COW,是一種用于程序設(shè)計(jì)中的優(yōu)化策略。

  • 讀寫分離,讀和寫分開(kāi)
  • 最終一致性
  • 使用另外開(kāi)辟空間的思路,來(lái)解決并發(fā)沖突

? JDK里的COW容器有兩種:

  • CopyOnWriteArrayList:適用于讀操作遠(yuǎn)遠(yuǎn)多于寫操作的場(chǎng)景,例如,緩存.
  • CopyOnWriteArraySet:線程安全的無(wú)序的集合,可以將它理解成線程安全的HashSet,適用于Set 大小通常保持很小,只讀操作遠(yuǎn)多于可變操作,需要在遍歷期間防止線程間的沖突

22 并發(fā)Queue

? 并發(fā)Queue:

  • ConcurrentLinkedQueue,高性能隊(duì)列,當(dāng)許多線程共享訪問(wèn)一個(gè)公共集合時(shí),ConcurrentLinkedQueue 是一個(gè)恰當(dāng)?shù)倪x擇。
  • BlockingQueue,阻塞隊(duì)列,是一個(gè)支持兩個(gè)附加操作的隊(duì)列,常用于生產(chǎn)者和消費(fèi)者的場(chǎng)景。

? ConcurrentLinkedQueue

  • 適用于高并發(fā)場(chǎng)景
  • 使用無(wú)鎖的方式,實(shí)現(xiàn)了高并發(fā)狀態(tài)下的高性能
  • 其性能好于BlockingQueue
  • 遵循先進(jìn)先出的原則

?常用方法:

  • Add()和offer()都是加入元素的方法
  • Poll()和peek()都是取頭元素節(jié)點(diǎn),區(qū)別在于前者會(huì)刪除元素,后者不會(huì)。

? BlockingQueue接口實(shí)現(xiàn):

  • ArrayBlockingQueue:基于數(shù)組的阻塞隊(duì)列實(shí)現(xiàn),在ArrayBlockingQueue內(nèi)部,維護(hù)了一個(gè)定長(zhǎng)的數(shù)組,以便緩存隊(duì)列中的數(shù)據(jù)對(duì)象,其內(nèi)部沒(méi)實(shí)現(xiàn)讀寫分離,也就意味著生產(chǎn)和消費(fèi)者不能完全并行,適用很多場(chǎng)景。
  • LinkedBlockingQueue:基于鏈表的阻塞隊(duì)列,同ArrayBlockingQueue類似,其內(nèi)部也維持著一個(gè)數(shù)據(jù)緩沖隊(duì)列(該隊(duì)列由一個(gè)鏈表構(gòu)成),LinkedBlockingQueue之所以能夠高效地處理并發(fā)數(shù)據(jù),是因?yàn)槠鋬?nèi)部實(shí)現(xiàn)采用分離鎖(讀寫分離兩個(gè)鎖),從而實(shí)現(xiàn)生產(chǎn)者和消費(fèi)者操作完全并行運(yùn)行。
  • PriorityBlockingQueue:基于優(yōu)先級(jí)別的阻塞隊(duì)列(優(yōu)先級(jí)的判斷通過(guò)構(gòu)造函數(shù)傳入的Compator對(duì)象來(lái)決定,也就是說(shuō)傳入隊(duì)列的對(duì)象必須實(shí)現(xiàn)Comparable接口),在實(shí)現(xiàn)PriorityBlockingQueue時(shí),內(nèi)部控制線程同步的鎖采用的是公平鎖
  • DelayQueue:帶有延遲時(shí)間的Queue,其中的元素只有當(dāng)其指定的延遲時(shí)間到了,才能夠從隊(duì)列中獲取到該元素。DelayQueue中的元素必須先實(shí)現(xiàn)Delayed接口,DelayQueue是一個(gè)沒(méi)有大小限制的隊(duì)列,應(yīng)用場(chǎng)景很多,比如對(duì)緩存超時(shí)的數(shù)據(jù)進(jìn)行移除、任務(wù)超時(shí)處理、空閑連接的關(guān)閉等等。
  • SynchronousQueue:一種沒(méi)有緩沖的隊(duì)列,生產(chǎn)者產(chǎn)生的數(shù)據(jù)直接會(huì)被消費(fèi)者獲取并且立刻消費(fèi)

23 多線程的設(shè)計(jì)模式

  • 基于并行設(shè)計(jì)模式演變而來(lái)
  • 屬于設(shè)計(jì)優(yōu)化的一部分
  • 是對(duì)一些常用的多線程結(jié)構(gòu)的總結(jié)和抽象
  • 常見(jiàn)的多線程設(shè)計(jì)模式有哪些?

24 Concurrent.util常用類

?CountDownLatch: 用于監(jiān)聽(tīng)某些初始化操作,等初始化執(zhí)行完畢后,通知主線程繼續(xù)工作。

?CycilcBarrier: 所有線程都準(zhǔn)備好后,才一起出發(fā),只要有一個(gè)人沒(méi)有準(zhǔn)備好,大家都等待。

Concurrent.util常用類
?定義:實(shí)現(xiàn)異步回調(diào),jdk針對(duì)該場(chǎng)景提供了一個(gè)實(shí)現(xiàn)的封裝,簡(jiǎn)化了調(diào)用
?適合場(chǎng)景:處理耗時(shí)的業(yè)務(wù)邏輯時(shí),可有效的減少系統(tǒng)的響應(yīng)時(shí)間,提高系統(tǒng)的吞吐量。

Concurrent.util常用類
?Semaphore:信號(hào)量,適合高并發(fā)訪問(wèn), 用于進(jìn)行訪問(wèn)流量的控制

?ReentrantLock(重入鎖)
重入鎖,在需要進(jìn)行同步的代碼部分加上鎖定,但不要忘記最后一定要釋放鎖定,不然會(huì)造成鎖永遠(yuǎn)無(wú)法釋放,其他線程永遠(yuǎn)也進(jìn)不來(lái)的結(jié)果。

? 鎖與等待/通知

  • 多線程間進(jìn)行協(xié)作工作則需要Object的wait()和notify()、notifyAll()方法進(jìn)行配合工作
  • 在使用鎖的時(shí)候,可以使用一個(gè)新的等待/通知的類,它就是Condition
  • 這個(gè)Condition是針對(duì)具體某一把鎖的

? 多Condition

  • 可通過(guò)一個(gè)Lock對(duì)象產(chǎn)生多個(gè)Condition進(jìn)行多線程間的交互
  • 使得部分需要喚醒的線程喚醒,其他線程則繼續(xù)等待通知。

? ReentrantReadWriteLock(讀寫鎖)

  • 其核心就是實(shí)現(xiàn)讀寫分離的鎖。尤其適應(yīng)在在高并發(fā)訪問(wèn)下讀多寫少的情況下,性能要遠(yuǎn)高于重入鎖。
  • 將讀寫鎖分離為讀鎖和寫鎖
  • 在讀鎖下,多個(gè)線程可以并發(fā)進(jìn)行訪問(wèn)
  • 在寫鎖下,只能一個(gè)一個(gè)的順序訪問(wèn)
  • 鎖優(yōu)化

25 線程池

image

? 使用 Executor 框架的原因:

  • 每次執(zhí)行任務(wù)創(chuàng)建線程 new Thread() 比較消耗性能,創(chuàng)建一個(gè)線程是比較耗時(shí)、耗資源的。
  • 調(diào)用 new Thread() 創(chuàng)建的線程缺乏管理,被稱為野線程,而且可以無(wú)限制的創(chuàng)建,線程之間的相互競(jìng)爭(zhēng)會(huì)導(dǎo)致過(guò)多占用系統(tǒng)資源而導(dǎo)致系統(tǒng)癱瘓,還有線程之間的頻繁交替也會(huì)消耗很多系統(tǒng)資源。
  • 接使用 new Thread() 啟動(dòng)的線程不利于擴(kuò)展,比如定時(shí)執(zhí)行、定期執(zhí)行、定時(shí)定期執(zhí)行、線程中斷等都不便實(shí)現(xiàn)

? 線程池的創(chuàng)建方式:

普通任務(wù)線程池

  • newFixedThreadPool(int nThreads) 方法,創(chuàng)建一個(gè)固定長(zhǎng)度的線程池。
    • 每當(dāng)提交一個(gè)任務(wù)就創(chuàng)建一個(gè)線程,直到達(dá)到線程池的最大數(shù)量,這時(shí)線程規(guī)模將不再變化。
    • 當(dāng)線程發(fā)生未預(yù)期的錯(cuò)誤而結(jié)束時(shí),線程池會(huì)補(bǔ)充一個(gè)新的線程。
  • newCachedThreadPool() 方法,創(chuàng)建一個(gè)可緩存的線程池。
    • 如果線程池的規(guī)模超過(guò)了處理需求,將自動(dòng)回收空閑線程。
    • 當(dāng)需求增加時(shí),則可以自動(dòng)添加新線程。線程池的規(guī)模不存在任何限制。
  • newSingleThreadExecutor() 方法,創(chuàng)建一個(gè)單線程的線程池。
    • 它創(chuàng)建單個(gè)工作線程來(lái)執(zhí)行任務(wù),如果這個(gè)線程異常結(jié)束,會(huì)創(chuàng)建一個(gè)新的來(lái)替代它。
    • 它的特點(diǎn)是,能確保依照任務(wù)在隊(duì)列中的順序來(lái)串行執(zhí)行。

定時(shí)任務(wù)線程池

  • newScheduledThreadPool(int corePoolSize) 方法,創(chuàng)建了一個(gè)固定長(zhǎng)度的線程池,而且以延遲或定時(shí)的方式來(lái)執(zhí)行任務(wù),類似 Timer 。
  • newSingleThreadExecutor() 方法,創(chuàng)建了一個(gè)固定長(zhǎng)度為 1 的線程池,而且以延遲或定時(shí)的方式來(lái)執(zhí)行任務(wù),類似 Timer 。

? 線程池的關(guān)閉方式

ThreadPoolExecutor 提供了兩個(gè)方法,用于線程池的關(guān)閉,分別是:

  • shutdown() 方法,不會(huì)立即終止線程池,而是要等所有任務(wù)緩存隊(duì)列中的任務(wù)都執(zhí)行完后才終止,但再也不會(huì)接受新的任務(wù)。
  • shutdownNow() 方法,立即終止線程池,并嘗試打斷正在執(zhí)行的任務(wù),并且清空任務(wù)緩存隊(duì)列,返回尚未執(zhí)行的任務(wù)

26 總結(jié)

??由于java多線程并發(fā)涉及到的知識(shí)點(diǎn)太多了,這邊不可能一一列全,不過(guò)我會(huì)在后續(xù)的更新中一一去補(bǔ)充完善,而且會(huì)涉及原理以及源碼層次的解析。謝謝觀看,有錯(cuò)誤歡迎指出更改??!

最后麻煩各位看官點(diǎn)個(gè)贊,謝謝支持!?。?/p>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Java-Review-Note——4.多線程 標(biāo)簽: JavaStudy PS:本來(lái)是分開(kāi)三篇的,后來(lái)想想還是整...
    coder_pig閱讀 1,780評(píng)論 2 17
  • layout: posttitle: 《Java并發(fā)編程的藝術(shù)》筆記categories: Javaexcerpt...
    xiaogmail閱讀 6,026評(píng)論 1 19
  • 必備的理論基礎(chǔ) 1.操作系統(tǒng)作用: 隱藏丑陋復(fù)雜的硬件接口,提供良好的抽象接口。 管理調(diào)度進(jìn)程,并將多個(gè)進(jìn)程對(duì)硬件...
    drfung閱讀 3,779評(píng)論 0 5
  • (六項(xiàng)精進(jìn))打卡第389天 姓名:汪何炯 公司.:寧波萬(wàn)尚進(jìn)出口有限公司 組別:340期【反省一組】學(xué)員兼隊(duì)長(zhǎng) ...
    汪何炯閱讀 206評(píng)論 0 0
  • 春天,草長(zhǎng)鶯飛,放飛的季節(jié),揚(yáng)起思緒,飄一會(huì)。 01 上帝關(guān)上一扇門,同時(shí)也會(huì)打開(kāi)另一扇門,多暖心的一句話。 只要...
    軍范律政閱讀 2,005評(píng)論 30 86

友情鏈接更多精彩內(nèi)容