天天看點

Java并發知識點總結Java并發知識點總結

Java并發知識點總結

synchronized

  1. 了解:synchronized 關鍵字解決的是多個線程之間通路資源的同步性,synchronized 關鍵字可以保證被它修飾的方法或者代碼塊在任意時刻隻能有一個線程執行。
    • Java 6 之前:重量級鎖,效率低下
    • Java 6 開始:有較大優化,引入了如自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術來減少鎖的開銷
  2. 使用方式:
    • 修飾執行個體方法:作用于目前對象執行個體加鎖,進入同步方法前要先獲得目前對象執行個體的鎖
    • 修飾靜态方法:給目前類加鎖
    • 修飾代碼塊:給指定對象加鎖
      例:手寫單例模式,解釋一下雙重檢驗方式實作單例模式的原理
      public class Singleton {
          private volatile static Singleton instance;
      
          public static Singleton getInstance() {
              if (instance == null) {
                  synchronized (instance) {
                      if (instance == null) {
                          instance = new Singleton();
                      }
                  }
              }
              return instance;
          }
      }
                 

      instance = new Singleton();

      這段代碼分三步執行:
      1. 為 instance 配置設定記憶體空間
      2. 初始化 instance
      3. 将 instance 指向配置設定的記憶體位址

      因為 JVM 存在指令重排序,執行順序可能變成 1 -> 3 -> 2 。在多線程環境下會導緻一個線程獲得還沒有初始化的執行個體。例如:線程 t1 執行了 1 和 3,此時 t2 調用 getInstance() 時發現 instance 不為空,是以傳回 instance,但此時 instance 還未被初始化。

      使用 volatile 可以禁止 JVM 的指令重排,保證在多線程環境下也能正常運作。

  3. 底層原理

    synchronized 關鍵字底層原理屬于 JVM 層面

    1. synchronized 同步語句塊的情況
      public class SynchronizedDemo01 {
          public void method() {
              synchronized (this) {
                  System.out.println("synchronized");
              }
          }
      }
                 
      通過 jdk 自帶的 javap 指令檢視 SynchronizedDemo01 類的相關位元組碼資訊:首先切換到類的對應目錄執行

      javac SynchronizedDemo01.java

      指令生成編譯後的 .class 檔案,然後執行

      javap -c -s -v -l SynchronizedDemo01.class

Java并發知識點總結Java并發知識點總結

從上面可以看出:synchronized 同步語句塊的實作是用 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代碼塊的開始位置,monitorexit 指令則指明同步代碼塊的結束位置。

  1. synchronized 修飾方法的情況
    public class SynchronizedDemo02 {
        public synchronized void method() {
            System.out.println("synchronized");
        }
    }
               
    位元組碼資訊:
Java并發知識點總結Java并發知識點總結
從上面可以看出:在同步方法上面存在一個 ACC_SYNCHRONIZED  辨別該方法是一個同步方法
           
  1. jdk 6之後的 synchronized 做的優化

    優化:偏向鎖、輕量級鎖、自旋鎖、适應性自旋鎖、鎖粗化等

    鎖的四種狀态:無鎖、偏向鎖、輕量級鎖、重量級鎖,它們會随着競争的激烈而逐漸更新,但是不可降級。

    參考:

    https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/Multithread/synchronized.md

  2. synchronized和ReentrantLock 的差別與聯系
    1. 兩者都是可重入鎖
    2. synchronized依賴于 JVM 而 ReentrantLock 依賴于 API
    3. ReentrantLock 比 synchronized 增加了一些進階功能
      1. 等待可中斷
      2. 可實作公平鎖
      3. 可實作選擇性通知(鎖可以綁定多個條件)

volatile

  1. Java記憶體模型:在目前的Java記憶體模型下,線程可以把變量儲存本地記憶體中,而不是直接在記憶體中進行讀寫。這就可能造成一個線程在記憶體中修改了一個變量的值,而另外一個線程還繼續使用它在寄存器中的變量值的拷貝,造成資料的不一緻
Java并發知識點總結Java并發知識點總結
  1. volatile的作用:
    • 告訴 JVM 這個變量是不穩定的,每次使用它都到主存中進行讀取
    • 防止指令重排序
Java并發知識點總結Java并發知識點總結
syncgronized 關鍵字 和 volatile 關鍵字比較
  • volatile 是線程同步的輕量級實作,是以性能比synchronized要更好。但是 volatile 關鍵字隻能用于變量而 synchronized 關鍵字可以修飾方法以及代碼塊
  • 多線程通路 volatile 關鍵字不會阻塞,而 synchronized 關鍵字可能會發生阻塞
  • volatile 關鍵字能保證資料的可見性,但不能保證資料的原子性。synchronized 關鍵字兩者都能保證
  • volatile 關鍵字主要用于解決變量在多個線程之間的可見性,而 synchronized 關鍵字解決的是多個線程之間通路資源的同步性

ThreadLocal

ThreadLocal

類可以讓每個線程綁定自己的值,可以将

ThreadLocal

類形象的比喻成存放資料的盒子,盒子中可以存儲每個線程的私有資料。

線程池

  1. 為什麼要使用線程池?

    池化技術的主要思想是為了減少每次擷取資源的消耗,提高對資源的使用率

  2. 使用線程池的好處:
    • 降低資源消耗:提高重複利用已建立的線程降低建立和銷毀造成的消耗
    • **提高響應速度:**當任務到達時,任務可以不需要等到線程建立就能立即執行
    • **提高線程的可管理性:**線程是稀缺資源,如果無限制的建立,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的配置設定,調優和監控。
  3. 實作 Runnable 和 Callable 接口的差別

    Runnable

    接口不會傳回結果或抛出檢查異常,但是**

    Callable

    接口**可以。是以,如果任務不需要傳回結果或抛出異常推薦使用

    Runnable

    接口,這樣代碼看起來會更加簡潔。
    • Runnable 接口
    @FunctionalInterface
    publicinterface Runnable {
       /**
        * 被線程執行,沒有傳回值也無法抛出異常
        */
        public abstract void run();
    }
               
    • Callable 接口
    @FunctionalInterface
    publicinterface Callable<V> {
        /**
         * 計算結果,或在無法這樣做時抛出異常。
         * @return 計算得出的結果
         * @throws 如果無法計算結果,則抛出異常
         */
        V call() throws Exception;
    }
               
    工具類

    Executors

    可以實作

    Runnable

    對象和

    Callable

    對象之間的互相轉換。
    • Executors.callable(Runnable task)

    • Executors.callable(Runnable task,T result)

  4. 執行

    execute()

    方法和

    submit()

    方法的差別:
    1. execute()

      方法用于送出不需要傳回值的任務,是以無法判斷任務被線程池執行成功與否
    2. submit()

      方法用于送出需要傳回值的任務。線程池會傳回一個

      Future

      類型的對象,通過這個

      Future

      對象可以判斷任務是否執行成功,并且可以通過

      Future

      get()

      方法來擷取傳回值,

      get()

      方法會阻塞目前線程知道任務完成,而使用

      get(long timeout,TimeUnit unit)

      方法則會阻塞目前線程一段時間後立即傳回,這時候有可能任務沒有執行完。
  5. 如何建立線程池

    《阿裡巴巴Java開發手冊》中強制線程池不允許使用 Executors 去建立,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明确線程池的運作規則,規避資源耗盡的風險

    Executor 傳回線程池對象的弊端如下:
    • FixedThreadPool 和 SingleThreadExecutor:允許請求的隊列長度為

      Integer.MAX_VALUE

      ,可能堆積大量的請求,進而導緻OOM
    • CachedThreadPool 和 ScheduledThreadPool:允許建立的線程數量為

      Integer.MAX_VALUE

      ,可能會建立大量線程,進而導緻OOM
    1. 通過構造方法實作
      Java并發知識點總結Java并發知識點總結
    2. 通過 Executor 架構的工具類 Executors 來實作,可以建立三種類型的ThreadPoolExecutor:
      • FixedThreadPool:傳回一個固定線程數量的線程池,且線程池中的線程數量始終不變。當有一個新任務送出時,線程池中若有空閑線程,則立即執行。若沒有,則新的任務會被暫存在一個任務隊列中,待有線程空閑時,便處理在任務隊列中的任務
      • SingleThreadPool:方法傳回一個隻有一個線程的線程池。若多餘一個任務被送出到線程池,任務會被儲存在一個任務隊列中,待線程空閑,按先入先出的順序執行隊列中的任務。
      • CachedThreadPool:該方法傳回一個可根據實際情況調整線程數量的線程池。線程池的線程數量不确定,若有空閑線程可以複用,則會優先使用可複用的線程。若所有線程均在工作,又有新任務送出,則會建立新的線程處理任務。所有線程在目前任務執行完畢後,将傳回線程池進行複用

        對應Executors工具類中的方法如圖所示,方法内部其實是調用了ThreadPoolExecutor的構造方法

        Java并發知識點總結Java并發知識點總結
  6. ThreadPoolExecutor 類分析

    ThreadPoolExecutor

    類中提供了四種構造方法,都是在最長的那個基礎上産生的。
    public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory,
                                  RejectedExecutionHandler handler) {
            if (corePoolSize < 0 ||
                maximumPoolSize <= 0 ||
                maximumPoolSize < corePoolSize ||
                keepAliveTime < 0)
                throw new IllegalArgumentException();
            if (workQueue == null || threadFactory == null || handler == null)
                throw new NullPointerException();
            this.acc = System.getSecurityManager() == null ?
                    null :
                    AccessController.getContext();
            this.corePoolSize = corePoolSize;
            this.maximumPoolSize = maximumPoolSize;
            this.workQueue = workQueue;
            this.keepAliveTime = unit.toNanos(keepAliveTime);
            this.threadFactory = threadFactory;
            this.handler = handler;
        }
               
    1. ThreadPoolExecutor

      構造函數重要參數分析

      ThreadPoolExecutor

      3 個最重要的參數:
      • corePoolSize

        :核心線程數
      • maximumPoolSize

        :最大線程數
      • workQueue

        :阻塞隊列
      其它參數:
      • keepAliveTime

        :普通線程空閑等待的時間超過了

        keepAliveTime

        會被回收銷毀
      • unit

        :

        keepAliveTime

        參數的時間機關
      • threadFactory

        :executor 建立新線程的時候會用到
      • handler

        :拒絕政策
    2. 拒絕政策:

      如果目前阻塞隊列已滿且線程池内的線程數達到了最大線程數,此時送出任務,線程池就會執行拒絕政策,

      ThreadPoolTaskExecutor

      定義一些政策:
      • AbortPolicy

        :拒絕任務并抛出異常
      • CallerRunsPolicy

        :使用調用者所線上程來運作任務
      • ThreadPoolExecutor.DiscardOldestPolicy

        :丢棄隊列裡最近的一個任務,并執行目前任務
      • ThreadPoolExecutor.DiscardPolicy

        :不處理直接丢棄任務
      預設使用的是**

      AbortPolicy

      **政策
    3. 線程池執行

      execute()

      方法的過程:
      1. 工作線程數小于核心線程數時,直接建立核心線程執行任務;
      2. 大于核心線程數時,将任務添加進等待隊列;
      3. 隊列滿時,建立非核心線程執行任務;
      4. 工作線程數大于最大線程數時,執行拒絕政策
Java并發知識點總結Java并發知識點總結
> ### 一個簡單的線程池Demo:`Runnable`+`ThreadPoolExecutor`
  >
  > 1. 首先建立一個 `Runnable` 接口的實作類 `MyRunnable.java`
  >
  >    ```java
  >    import java.util.Date;
  >    
  >    public class MyRunnable implements Runnable{
  >    
  >        private String command;
  >    
  >        public MyRunnable(String s) {
  >            this.command = s;
  >        }
  >    
  >        @Override
  >        public void run() {
  >            System.out.println(Thread.currentThread().getName() + " Start. Time = " + new Date());
  >            processCommand();
  >            System.out.println(Thread.currentThread().getName() + " End. Time = " + new Date());
  >        }
  >    
  >        private void processCommand() {
  >            try {
  >                Thread.sleep(5000);
  >            } catch (InterruptedException e) {
  >                e.printStackTrace();
  >            }
  >        }
  >    
  >        @Override
  >        public String toString() {
  >            return "MyRunnable{" +
  >                    "command='" + command + '\'' +
  >                    '}';
  >        }
  >    }
  >    
  >    ```
  >
  > 2. 建立`ThreadPoolExecutorDemo.java`
  >
  >    ```java
  >    import java.util.concurrent.ArrayBlockingQueue;
  >    import java.util.concurrent.ThreadPoolExecutor;
  >    import java.util.concurrent.TimeUnit;
  >    
  >    public class ThreadPoolExecutorDemo {
  >        private static final int CORE_POOL_SIZE = 5;
  >        private static final int MAX_POOL_SIZE = 10;
  >        private static final int QUEUE_CAPACITY = 100;
  >        private static final Long KEEP_ALIVE_TIME = 1L;
  >        public static void main(String[] args) {
  >    
  >            //使用阿裡巴巴推薦的建立線程池的方式
  >            //通過ThreadPoolExecutor構造函數自定義參數建立
  >            ThreadPoolExecutor executor = new ThreadPoolExecutor(
  >                    CORE_POOL_SIZE,
  >                    MAX_POOL_SIZE,
  >                    KEEP_ALIVE_TIME,
  >                    TimeUnit.SECONDS,
  >                    new ArrayBlockingQueue<>(QUEUE_CAPACITY),
  >                    new ThreadPoolExecutor.CallerRunsPolicy());
  >    
  >            for (int i = 0; i < 10; i++) {
  >                //建立WorkerThread對象(WorkerThread類實作了Runnable 接口)
  >                Runnable worker = new MyRunnable("" + i);
  >                //執行Runnable
  >                executor.execute(worker);
  >            }
  >            //終止線程池
  >            executor.shutdown();
  >            while (!executor.isTerminated()) {
  >            }
  >            System.out.println("Finished all threads");
  >        }
  >    }
  >    
  >    ```
           

5 Atomic 原子類

  1. 介紹:

    原子類就是具有原子操作特征的類。并發包

    java.util.concurrent

    的原子類都存放在

    java.util.concurrent.atomic

    下。

    [外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-Ea53l9hG-1595302640206)(C:\Users\ykyu3\AppData\Roaming\Typora\typora-user-images\image-20200721110829375.png)]

  2. JUC 包中的原子類分為4類:
    • 基本類型
      • AtomicInteger
      • AtomicLong
      • AtomicBoolean
    • 數組類型
      • AtomicIntegerArray
      • AtomicLongArray
      • AtomicReferenceArray
    • 引用類型
      • AtomicReference
      • AtomicStampedReference:原子更新引用類型裡的字段原子類
      • AtomicMarkableReference:原子更新帶有标記為的引用類型
    • 對象的屬性修改類型
      • AtomicIntegerFieldUpdater:原子更新整型字段的更新器
      • AtomicLongFieldUpdater:原子更新長整型字段的更新器
      • AtomicStampedReference:原子更新帶有版本号的引用類型