亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java多線程與并發筆記

發布時間:2020-07-21 08:03:09 來源:網絡 閱讀:1058 作者:ZeroOne01 欄目:編程語言

synchronized

synchronized主要是用于解決線程安全問題的,而線程安全問題的主要誘因有如下兩點:

  • 存在共享數據(也稱臨界資源)
  • 存在多條線程共同操作這些共享數據

解決線程安全問題的根本方法:

  • 同一時刻有且只有一個線程在操作共享數據,其他線程必須等到該線程處理完數據后再對共享數據進行操作

所以互斥鎖是解決問題的辦法之一,互斥鎖的特性如下:

互斥性:即在同一時間只允許一個線程持有某個對象鎖,通過這種特性來實現多線程的協調機制,這樣在同一時間只有一個線程對需要同步的代碼塊(復合操作)進行訪問。互斥性也稱為操作的原子性
可見性:必須確保在鎖被釋放之前,對共享變量所做的修改,對于隨后獲得該鎖的另一個線程是可見的(即在獲得鎖時應獲得最新共享變量的值),否則另一個線程可能是在本地緩存的某個副本上繼續操作,從而引起數據不一致問題

而synchronized就可以實現互斥鎖的特性,不過需要注意的是synchronized鎖的不是代碼,而是對象。

根據獲取的鎖可以分為兩類:

  • 對象鎖:獲取對象鎖有兩種用法
    1. 同步代碼塊(synchronized(this),synchronized(類實例對象)),鎖是小括號()中的實例對象
    2. 同步非靜態方法(synchronized method),鎖是當前的實例對象,即this
  • 類鎖:獲取類鎖也有兩種用法
    1. 同步代碼塊(synchronized(類.class)),鎖是小括號()中的類對象(Class對象)
    2. 同步靜態方法(synchronized static method),鎖是當前對象的類對象(Class對象)

對象鎖和類鎖的總結:

  1. 有線程訪問對象的同步塊代碼時,另外的線程可以訪問該對象的非同步代碼塊
  2. 若鎖住的是同一個對象,一個線程在訪問對象的同步代碼塊時,另一個訪問對象的同步代碼塊的線程會被阻塞
  3. 若鎖住的是同一個對象,一個線程在訪問對象的同步方法時,另一個訪問對象同步方法的線程會被阻塞
  4. 若鎖住的是同一個對象,一個線程在訪問對象的同步塊時,另一個訪問對象同步方法的線程會被阻塞,反之亦然
  5. 同一個類的不同對象的對象鎖互不干擾
  6. 類鎖由于也是一把特殊的對象鎖,因此表現與上述1,2,3,4一致,而由于一個類只有一把對象鎖,所以同一個類的不同對象使用類鎖將會是同步的
  7. 類鎖和對象鎖互補干擾,因為類對象和實例對象不是同一個對象

synchronized底層實現原理

實現synchronized需要依賴兩個基礎概念:

  • Java對象頭
  • Monitor

Java對象在內存中的布局主要分為三塊區域:

  • 對象頭
  • 實例數據
  • 對齊填充

synchronized使用的鎖對象是存儲在Java對象頭里的,對象頭結構如下:
Java多線程與并發筆記

由于對象頭信息是與對象自身定義的數據沒有關系的額外存儲成本,考慮到JVM的空間效率,Mark Word被設計為非固定的數據結構以便存儲更多有效的數據,它會根據對象自身的狀態賦予自己的存儲空間:
Java多線程與并發筆記

簡單介紹了對象頭,接著我們來了解一下Monitor,每個Java對象天生自帶了一把看不見的鎖,它叫做內部鎖或Monitor鎖。Monitor的主要實現代碼在ObjectMonitor.hpp中:
Java多線程與并發筆記

Monitor鎖的競爭、獲取與釋放:
Java多線程與并發筆記

然后我們從字節碼層面上看一下synchronized,將如下代碼通過javac編譯成class文件:

package com.example.demo.thread;

/**
 * @author 01
 * @date 2019-07-20
 **/
public class SyncBlockAndMethod {

    public void syncsTask() {
        synchronized (this) {
            System.out.println("Hello syncsTask");
        }
    }

    public synchronized void syncTask() {
        System.out.println("Hello syncTask");
    }
}

然后通過 javap -verbose 將class文件反編譯成可閱讀的字節碼內容,如下:

Classfile /E:/Java_IDEA/demo/src/main/java/com/example/demo/thread/SyncBlockAndMethod.class
  Last modified 2019年7月20日; size 637 bytes
  MD5 checksum 7600723349daa088a5353acd84c80fa5
  Compiled from "SyncBlockAndMethod.java"
public class com.example.demo.thread.SyncBlockAndMethod
  minor version: 0
  major version: 55
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #6                          // com/example/demo/thread/SyncBlockAndMethod
  super_class: #7                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 3, attributes: 1
Constant pool:
   #1 = Methodref          #7.#18         // java/lang/Object."<init>":()V
   #2 = Fieldref           #19.#20        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #21            // Hello syncsTask
   #4 = Methodref          #22.#23        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = String             #24            // Hello syncTask
   #6 = Class              #25            // com/example/demo/thread/SyncBlockAndMethod
   #7 = Class              #26            // java/lang/Object
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               syncsTask
  #13 = Utf8               StackMapTable
  #14 = Class              #27            // java/lang/Throwable
  #15 = Utf8               syncTask
  #16 = Utf8               SourceFile
  #17 = Utf8               SyncBlockAndMethod.java
  #18 = NameAndType        #8:#9          // "<init>":()V
  #19 = Class              #28            // java/lang/System
  #20 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #21 = Utf8               Hello syncsTask
  #22 = Class              #31            // java/io/PrintStream
  #23 = NameAndType        #32:#33        // println:(Ljava/lang/String;)V
  #24 = Utf8               Hello syncTask
  #25 = Utf8               com/example/demo/thread/SyncBlockAndMethod
  #26 = Utf8               java/lang/Object
  #27 = Utf8               java/lang/Throwable
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V
{
  public com.example.demo.thread.SyncBlockAndMethod();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0

  public void syncsTask();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter                      // 指向同步代碼塊的開始位置
         4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: ldc           #3                  // String Hello syncsTask
         9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        12: aload_1
        13: monitorexit                       // 指向同步代碼塊的結束位置,monitorenter和monitorexit之間就是同步代碼塊
        14: goto          22
        17: astore_2
        18: aload_1
        19: monitorexit                       // 若代碼發生異常時就會執行這句指令釋放鎖
        20: aload_2
        21: athrow
        22: return
      Exception table:
         from    to  target type
             4    14    17   any
            17    20    17   any
      LineNumberTable:
        line 10: 0
        line 11: 4
        line 12: 12
        line 13: 22
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 17
          locals = [ class com/example/demo/thread/SyncBlockAndMethod, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4

  public synchronized void syncTask();
    descriptor: ()V
    flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED  // 用于標識是一個同步方法,不需要像同步塊那樣需要通過顯式的字節碼指令去標識哪里需要獲取鎖,哪里需要釋放鎖。同步方法無論是正常執行還是發生異常都會釋放鎖
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #5                  // String Hello syncTask
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 16: 0
        line 17: 8
}
SourceFile: "SyncBlockAndMethod.java"

什么是重入:

從互斥鎖的設計上來說,當一個線程試圖操作一個由其他線程持有的對象鎖的臨界資源時,將會處于阻塞狀態,但當一個線程再次請求自己持有對象鎖的臨界資源時,這種情況屬于重入

為什么會對synchronized嗤之以鼻:

  • 在早期版本中,synchronized屬于重量級鎖效率低下,因為依賴于Mutex Lock實現,因為線程之間的切換需要從用戶態轉換到核心態,開銷較大。不過在Java6以后引入了許多鎖優化機制,synchronized性能已經得到了很大的提升

鎖優化之自旋鎖:

許多情況下,共享數據的鎖定狀態持續時間較短,切換線程不值得。于是自旋鎖應運而生,所謂自旋就是通過讓線程執行忙循環等待鎖的釋放,從而不讓出CPU時間片,例如while某個標識變量

缺點:若鎖被其他線程長時間占用,將會帶來許多性能上的開銷,所以一般超過指定的自旋次數就會將線程掛起處于阻塞狀態

鎖優化之自適應自旋鎖:

自適應自旋鎖與普通自旋鎖不同的就是可以自適應自旋次數,即自旋次數不再固定。而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定

鎖優化之鎖消除,鎖消除是JVM另一種鎖優化,這種優化更徹底:

在JIT編譯時,對運行上下文進行掃描,去除不可能存在資源競爭的鎖。這種方式可以消除不必要的鎖,可以減少毫無意義的請求鎖時間

關于鎖消除,我們可以看一個例子,代碼如下:

public class StringBufferWithoutSync {

    public void add(String str1, String str2) {
        //StringBuffer是線程安全,由于sb只會在append方法中使用,不可能被其他線程引用
        //因此sb屬于不可能共享的資源,JVM會自動消除內部的鎖
        StringBuffer sb = new StringBuffer();
        sb.append(str1).append(str2);
    }

    public static void main(String[] args) {
        StringBufferWithoutSync withoutSync = new StringBufferWithoutSync();
        for (int i = 0; i < 1000; i++) {
            withoutSync.add("aaa", "bbb");
        }
    }
}

鎖優化之鎖粗化,我們再來了解鎖粗化的概念,有些情況下可能會需要頻繁且重復進行加鎖和解鎖操作,例如同步代碼寫在循環語句里,此時JVM會有鎖粗化的機制,即通過擴大加鎖的范圍,以避免反復加鎖和解鎖操作。代碼示例:

public class CoarseSync {

    public static String copyString100Times(String target){
        int i = 0;
        // JVM會將鎖粗化到外部,使得重復的加解鎖操作只需要進行一次
        StringBuffer sb = new StringBuffer();
        while (i < 100){
            sb.append(target);
        }

        return sb.toString();
    }
}

synchronized鎖存在四種狀態:

  • 無鎖、偏向鎖、輕量級鎖、重量級鎖
  • 鎖膨脹的方向:無鎖 -> 偏向鎖 -> 輕量級鎖 -> 重量級鎖
  • 鎖膨脹存在跨級現象,例如直接從無鎖膨脹到重量級鎖

偏向鎖:

大多數情況下,鎖不存在多線程競爭,總是由同一線程多次獲得,為了減少同一線程獲取鎖的代價,就會使用偏向鎖

核心思想:
如果一個線程獲得了鎖,那么鎖就進入偏向模式,此時Mark Word的結構也變為偏向鎖結構,當該線程再次請求鎖時,無需再做任何同步操作,即獲取鎖的過程只需要檢查Mark Word的鎖標記位為偏向鎖以及當前線程id等于Mark Word的ThreadID即可,這樣就省去了大量有關鎖申請的操作,那么這個鎖也就偏向于該線程了

偏向鎖不適用于鎖競爭比較激烈的多線程場合

輕量級鎖:

輕量級鎖是由偏向鎖升級而來,偏向鎖運行在一個線程進入同步塊的情況下,當有第二個線程加入鎖競爭時,偏向鎖就會升級為輕量級鎖

適用場景:線程交替執行同步塊

若存在線程同一時間訪問同一鎖的情況,就會導致輕量級鎖膨脹為重量級鎖

輕量級鎖的加鎖過程:

  1. 在代碼進入同步塊的時候,如果同步對象鎖狀態為無鎖狀態(鎖標志位為“01”狀態),虛擬機首先將在當前線程的棧幀中建立一個名為鎖記錄(LockRecord)的空間,用于存儲鎖對象目前的Mark Word的拷貝,官方稱之為Displaced Mark Word。這時候線程堆棧與對象頭的狀態如下圖所示:
    Java多線程與并發筆記

  2. 拷貝對象頭中的Mark Word復制到鎖記錄中
  3. 拷貝成功后,虛擬機將使用CAS操作嘗試將對象的Mark Word更新為指向Lock Record的指針,并將Lock record里的owner指針指向object mark word。如果更新成功,則執行步驟4,否則執行步驟5
  4. 如果這個更新動作成功了,那么這個線程就擁有了該對象的鎖,并且對象Mark Word的鎖標志位設置為“00",即表示此對象處于輕量級鎖定狀態,這時候線程堆棧與對象頭的狀態如下圖所示:
    Java多線程與并發筆記

  5. 如果這個更新操作失敗了,虛擬機首先會檢查對象的Mark Word是否指向當前線程的棧幀,如果是就說明當前線程已經擁有了這個對象的鎖,那就可以直接進入同步塊繼續執行。否則說明多個線程競爭鎖,輕量級鎖就要膨脹為重量級鎖,鎖標志的狀態值變為“10",Mark Word中存儲的就是指向重量級鎖(互斥量)的指針,后面等待鎖的線程也要進入阻塞狀態。而當前線程便嘗試使用自旋來獲取鎖,自旋咱們前面講過,就是為了不讓線程阻塞,而采用循環去獲取鎖的過程

輕量級鎖的解鎖過程:

  1. 通過CAS操作嘗試把線程中復制的Displaced Mark Word對象替換當前的Mark Word
  2. 如果替換成功,整個同步過程就完成了
  3. 如果替換失敗,說明有其他線程嘗試過獲取該鎖(此時鎖己膨脹),那就要在釋放鎖的同時,喚醒被掛起的線程

鎖的內存語義:

當線程釋放鎖時,Java內存模型會把該線程對應的本地內存中的共享變量刷新到主內存中;而當線程獲取鎖時,Java內存模型會把該線程對應的本地內存置為無效,從而使得被監視器保護的臨界區代碼必須從主內存中讀取共享變量
Java多線程與并發筆記

偏向鎖、輕量級鎖、重量級鎖的匯總:
Java多線程與并發筆記


synchronized和ReentrantLock的區別

在JDK1.5之前,synchronized是Java唯一的同步手段,而在1.5之后則有了ReentrantLock類(重入鎖):

  • 位于java.util.concurrent.locks包
  • 和CountDownLatch、FuturaTask、Semaphore一樣基于AQS框架實現
  • 能夠實現比synchronized更細粒度的控制,如控制fairness
  • 調用lock之后,必須調用unlock釋放鎖
  • 在JDK6之后性能未必比synchronized高,并且也是可重入的

ReentrantLock公平性的設置:

  • ReentrantLock fairLock = new ReentrantLock(true);
  • 參數為true時,傾向于將鎖賦予等待時間最久的線程,即設置為所謂的公平鎖,公平性是減少線程饑餓的一個辦法
  • 公平鎖:獲取鎖的順序按先后調用lock方法的順序,公平鎖需慎用,因為會影響性能
  • 非公平鎖:線程搶占鎖的順序不一定,與調用順序無關,看運氣
  • synchronized是非公平鎖

ReentrantLock的好處在于將鎖對象化了,因此可以實現synchronized難以實現的邏輯,例如:

  • 判斷是否有線程,或者某個特定線程,在排隊等待獲取鎖
  • 帶超時的獲取鎖的嘗試
  • 感知有沒有成功獲取鎖

如果說ReentrantLock將synchronized轉變為了可控的對象,那么是否能將wait、notify及notifyall等方法對象化,答案是有的,即Condition:

  • 位于java.util.concurrent.locks包
  • 可以通過ReentrantLock的newCondition方法獲取該Condition對象實例

synchronized和ReentrantLock的區別:

  • synchronized是關鍵字,ReentrantLock是類
  • ReentrantLock可以對獲取鎖的等待時間進行設置,避免死鎖
  • ReentrantLock可以獲取各種鎖的信息
  • ReentrantLock可以靈活地實現多路通知
  • 內部機制:synchronized操作的是Mark Word,而ReentrantLock底層是調用Unsafe類的park方法來加鎖

jmm的內存可見性

Java內存模型(JMM):

Java內存模型(Java Memory Model,簡稱JMM)本身是一種抽象的概念,并不真實存在,它描述的是一組規則或規范,通過這組規范定義了程序中各個變量(包括實例字段,靜態字段和構成數組對象的元素)的訪問方式

Java多線程與并發筆記

JMM中的主內存(即堆空間):

  • 存儲Java實例對象
  • 包括成員變量、類信息、常量、靜態變量等
  • 屬于數據共享的區域,多線程并發操作時會引發線程安全問題

JMM中的工作內存(即本地內存,或線程棧):

  • 存儲當前方法的所有本地變量信息,本地變量對其他線程不可見
  • 字節碼行號指示器、Native方法信息
  • 屬于線程私有數據區域,不存在線程安全問題

JMM與Java內存區域劃分(即Java內存結構)是不同的概念層次:

  • JMM描述的是一組規則,通過這組控制程序中各個變量在共享數據區域和私有數據區域的訪問方式,JMM是圍繞原子性、有序性及可見性展開的
  • 兩者相似點:存在共享數據區域和私有數據區域

主內存與工作內存的數據存儲類型以及操作方式歸納:

  • 方法里的基本數據類型本地變量將直接存儲在工作內存的棧幀結構中
  • 引用類型的本地變量,則是其引用存儲在工作內存中,而具體的實例存儲在主內存中
  • 對象的成員變量、static變量、類信息均會被存儲在主內存中
  • 主內存共享的方式是線程各拷貝一份數據到工作內存,操作完成后刷新回主內存

JMM如何解決可見性問題:
Java多線程與并發筆記

指令重排序需要滿足的條件:

  • 在單線程環境下不能改變程序運行的結果
  • 存在數據依賴關系的不允許重排序
  • 以上兩點可以歸結為:無法通過happens-before原則推導出來的,才能進行指令的重排序

什么是Java內存模型中的happens-before:

  • 如果兩個操作不滿足下述任意一個happens-before原則,那么這兩個操作就沒有順序的保障,JVM可以對這兩個操作進行重排序
  • 如果操作A happens-before 操作B,那么操作A在內存上所做的操作對操作B都是可見的
  • 若A操作的結果需要對B操作可見,則A與B存在happens-before關系

happens-before的八大原則:

  1. 程序次序規則:一個線程內,按照代碼順序,書寫在前面的操作先行發生于書寫在后面的操作
  2. 鎖定規則:一個unLock操作先行發生于后面對同一個鎖的lock操作
  3. volatile變量規則:對一個變量的寫操作先行發生于后面對這個變量的讀操作(保證了可見性)
  4. 傳遞規則:如果操作A先行發生于操作B,而操作B又先行發生于操作C,則可以得出操作A先行發生于操作C
  5. 線程啟動規則:Thread對象的start()方法先行發生于此線程的每一個動作
  6. 線程中斷規則:對線程interrupt()方法的調用先行發生于被中斷線程的代碼檢測到中斷事件的發生
  7. 線程終結規則:線程中所有的操作都先行發生于線程的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到線程已經終止執行
  8. 對象終結規則:一個對象的初始化完成先行發生于他的finalize()方法的開始

volatile:

  • 是JVM提供的輕量級同步機制
  • JVM保證被volatile修飾的共享變量對所有線程總是可見的
  • 禁止指令的重排序優化
  • 使用volatile不能保證線程安全,需要變量的操作滿足原子性

volatile變量為何立即可見?簡單來說:

  • 當寫一個volatile變量時,JMM會把該線程對應的工作內存中的共享變量值刷新到主內存中
  • 當讀取一個volatile變量時,JMM會把該線程對應的工作內存置為無效,那么就需要從主內存中重新讀取該變量

volatile變量如何禁止重排序優化:

  • 對此我們需要先了解內存屏障(Memory Barrier),其作用有二:
    1. 保證特定操作的執行順序
    2. 保證某些變量的內存可見性
  • 通過插入內存屏障指令來禁止對內存屏障前后的指令執行重排序優化
  • 強制刷出各種CPU的緩存數據,因此任何CPU上的線程都能讀取到這些數據的最新版本

volatile和synchronized的區別:

  1. volatile本質是在告訴JVM當前變量在寄存器(工作內存)中的值是不確定的,需要從主存中讀取;synchronized則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程被阻塞住直到該線程完成變量操作為止
  2. volatile僅能使用在變量級別;synchronized則可以使用在變量、方法和類級別
  3. volatile僅能實現變量的修改可見性,不能保證原子性;而synchronized則可以保證變量修改的可見性和原子性
  4. volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞
  5. volatile標記的變量不會被編譯器優化;synchronized標記的變量可以被編譯器優化

CAS

CAS(Compare and Swap)是一種線程安全性的方法:

  • 支持原子更新操作,適用于計數器,序列發生器等場景
  • 屬于樂觀鎖機制,號稱lock-free
  • CAS操作失敗時由開發者決定是繼續嘗試,還是執行別的操作

CAS思想:

  • 包含三個操作數:內存位置(V)、預期原值(A)和新值(B)

CAS多數情況下對開發者來說是透明的:

  • J.U.C的atomic包提供了常用的原子性數據類型以及引用、數組等相關原子類型和更新操作工作,是很多線程安全程序的首選
  • Unsafe類雖然提供CAS服務,但因能夠操縱任意內存地址讀寫而有隱患
  • Java9以后,可以使用Variable Handle API來代替Unsafe

缺點:

  • 若循環時間長,則開銷很大
  • 只能保證一個共享變量的原子操作
  • 存在ABA問題,可以通過使用AtomicStampedReference來解決,但由于是通過版本標記來解決所以存在一定程度的性能損耗

Java線程池

利用Executors創建不同的線程池滿足不同場景的需求:

  1. newFixedThreadPool(int nThreads):指定工作線程數量的線程池
  2. newCachedThreadPool():處理大量短時間工作任務的線程池,特點:
  3. 試圖緩存線程并重用,當無緩存線程可用時,就會創建新的工作線程
  4. 如果線程閑置的時間超過閾值,則會被終止并移出緩存
  5. 系統長時間閑置的時候,不會消耗什么資源
  6. newSingleThreadExecutor():創建唯一的工作者線程來執行任務,如果線程異常結束,會有另一個線程取代它
  7. newSingleThreadScheduledExecutor()與newScheduledThreadPool(int corePoolSize):定時或者周期性的工作調度,兩者的區別在于單一工作線程還是多個線程
  8. JDK8新增的newWorkStealingPool():內部會構建ForkJoinPool ,利用working-stealing算法,并行地處理任務,不保證處理順序
    • working-stealing算法:某個線程從其他線程的任務隊列里竊取任務來執行

Fork/Join框架(JDK7提供):

  • 是一個可以把大任務分割成若干個小任務并行執行,最終匯總每個小任務結果后得到大任務結果的框架

Java多線程與并發筆記

為什么要使用線程池:

  1. 減低資源消耗,避免頻繁地創建和銷毀線程
  2. 提高線程的可管理性,例如可控的線程數量,線程狀態的監控和統一創建/銷毀線程

Executor的框架:
Java多線程與并發筆記

J.U.C的三個Executor接口:

  • Executor:運行新任務的簡單接口,將任務提交和任務執行細節解耦
  • ExecutorService:具備管理執行器和任務生命周期的方法,提交任務機制更完善
  • ScheduleExecutorService:支持Future和定期執行任務

線程池執行任務流程圖:
Java多線程與并發筆記

ThreadPoolExecutor的七個構造器參數:

  • int corePoolSize:核心線程數
  • int maximumPoolSize:最大線程數
  • long keepAliveTime:線程空閑存活時間
  • TimeUnit unit:存活時間的單位
  • BlockingQueue&lt;Runnable&gt; workQueue:任務等待隊列
  • ThreadFactory threadFactory:線程創建工廠,用于創建新線程
  • RejectedExecutionHandler handler:任務拒絕策略
    • AbortPolicy:直接拋出異常,這是默認策略
    • CallerRunsPolicy:使用調用者所在的線程來執行任務
    • DiscardOldestPolicy:丟棄隊列中最靠前的任務,并執行當前任務
    • DiscardPolicy:直接丟棄提交的任務
    • 另外可以實現RejectedExecutionHandler接口來自定義handler

新任務提交execute執行后的判斷:

  • 如果運行的線程少于corePoolSize ,則創建新線程來處理任務,即使線程池中的其他線程是空閑的;
  • 如果線程池中的線程數量大于等于corePoolSize且小于maximumPoolSize,則只有當workQueue滿時才創建新的線程去處理任務;
  • 如果設置的corePoolSize和maximumPoolSize相同,則創建的線程池的大小是固定的,這時如果有新任務提交,若workQueue未滿,則將請求放入workQueue中,等待有空閑的線程去從workQueue中取任務并處理;
  • 如果運行的線程數量大于等于maximumPoolSize,這時如果workQueue已經滿了,則通過handler所指定的策略來處理任務;

execute執行流程圖:
Java多線程與并發筆記

線程池的狀態:

  • RUNNING:能接受新提交的任務,并且也能處理阻塞隊列中的任務
  • SHUTDOWN:不再接受新提交的任務,但可以處理存量任務(調用shutdown方法)
  • STOP:不再接受新提交的任務,也不處理存量任務(調用shutdownNow方法)
  • TIDYING:所有的任務都已終止
  • TERMINATED:terminated() 方法執行完后進入該狀態

線程池狀態轉換圖:
Java多線程與并發筆記

線程池中工作線程的生命周期:
Java多線程與并發筆記

關于線程池大小如何選定參考:

  • CPU密集型任務:線程數 = 按照CPU核心數或者CPU核心數 + 1設定
  • I/O密集型任務:線程數 = CPU核心數 * (1 + 平均等待時間 / 平均工作時間)
向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

新竹县| 泰州市| 万全县| 花垣县| 玉山县| 海兴县| 南漳县| 永济市| 沾化县| 新兴县| 安国市| 溧水县| 米易县| 福贡县| 维西| 平邑县| 邓州市| 平乐县| 仁寿县| 屏东市| 博湖县| 淳化县| 中江县| 方城县| 赤城县| 陆良县| 郓城县| 德兴市| 财经| 洞口县| 清水河县| 耿马| 鄂州市| 南郑县| 北宁市| 木里| 茌平县| 城步| 宁城县| 泗阳县| 武定县|