長文慎入-探索Java併發編程與高併發解決方案

所有示例代碼,請見/下載於
https://github.com/Wasabi1234/concurrency
長文慎入-探索Java併發編程與高併發解決方案
高併發處理的思路及手段
長文慎入-探索Java併發編程與高併發解決方案

#1 基本概念
##1.1 併發
同時擁有兩個或者多個線程,如果程序在單核處理器上運行多個線程將交替地換入或者換出內存,這些線程是同時“存在"的,每個線程都處於執行過程中的某個狀態,如果運行在多核處理器上,此時,程序中的每個線程都將分配到一個處理器核上,因此可以同時運行.
##1.2 高併發( High Concurrency)
互聯網分佈式系統架構設計中必須考慮的因素之一,通常是指,通過設計保證系統能夠同時並行處理很多請求.
##1.3 區別與聯繫

  • 併發: 多個線程操作相同的資源,保證線程安全,合理使用資源
  • 高併發:服務能同時處理很多請求,提高程序性能
    #2 CPU
    ##2.1 CPU 多級緩存
    長文慎入-探索Java併發編程與高併發解決方案
  • 爲什麼需要CPU cache
    CPU的頻率太快了,快到主存跟不上
    如此,在處理器時鐘週期內,CPU常常需要等待主存,浪費資源。所以cache的出現,是爲了緩解CPU和內存之間速度的不匹配問題(結構:cpu-> cache-> memory ).
  • CPU cache的意義
    1) 時間局部性
    如果某個數據被訪問,那麼在不久的將來它很可能被再次訪問
    2) 空間局部性
    如果某個數據被訪問,那麼與它相鄰的數據很快也可能被訪問
    ##2.2 緩存一致性(MESI)
    用於保證多個 CPU cache 之間緩存共享數據的一致
  • M-modified被修改
    該緩存行只被緩存在該 CPU 的緩存中,並且是被修改過的,與主存中數據是不一致的,需在未來某個時間點寫回主存,該時間是允許在其他CPU 讀取主存中相應的內存之前,當這裏的值被寫入主存之後,該緩存行狀態變爲 E
  • E-exclusive獨享
    緩存行只被緩存在該 CPU 的緩存中,未被修改過,與主存中數據一致
    可在任何時刻當被其他 CPU讀取該內存時變成 S 態,被修改時變爲 M態
  • S-shared共享
    該緩存行可被多個 CPU 緩存,與主存中數據一致
  • I-invalid無效
    長文慎入-探索Java併發編程與高併發解決方案
  • 亂序執行優化
    處理器爲提高運算速度而做出違背代碼原有順序的優化
    ##併發的優勢與風險
    長文慎入-探索Java併發編程與高併發解決方案
    #3 項目準備
    ##3.1 項目初始化
    自定義4個基本註解
    隨手寫個測試類
    運行正常
    ##3.2 併發模擬-Jmeter壓測
    長文慎入-探索Java併發編程與高併發解決方案
    長文慎入-探索Java併發編程與高併發解決方案
    添加
    log view 下當前日誌信息
    圖形結果
    ##3.3 併發模擬-代碼
    ###CountDownLatch
    可阻塞線程,並保證當滿足特定條件時可繼續執行
    ###Semaphore(信號量)
    可阻塞線程,控制同一時間段內的併發量
    以上二者通常和線程池搭配

下面開始做併發模擬

package com.mmall.concurrency;

import com.mmall.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * @author shishusheng
 * @date 18/4/1
 */
@Slf4j
@NotThreadSafe
public class ConcurrencyTest {

    /**
     * 請求總數
     */
    public static int clientTotal = 5000;

    /**
     * 同時併發執行的線程數
     */
    public static int threadTotal = 200;

    public static int count = 0;

    public static void main(String[] args) throws Exception {
        //定義線程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //定義信號量,給出允許併發的線程數目
        final Semaphore semaphore = new Semaphore(threadTotal);
        //統計計數結果
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        //將請求放入線程池
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    //信號量的獲取
                    semaphore.acquire();
                    add();
                    //釋放
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        //關閉線程池
        executorService.shutdown();
        log.info("count:{}", count);
    }

    /**
     * 統計方法
     */
    private static void add() {
        count++;
    }
}

運行發現結果隨機,所以非線程安全
#4線程安全性
##4.1 線程安全性
當多個線程訪問某個類時,不管運行時環境採用何種調度方式或者這些進程將如何交替執行,並且在主調代碼中不需要任何額外的同步或協同,這個類都能表現出正確的行爲,那麼就稱這個類是線程安全的
##4.2 原子性
###4.2.1 Atomic 包

  • AtomicXXX:CAS,Unsafe.compareAndSwapInt
    提供了互斥訪問,同一時刻只能有一個線程來對它進行操作
    
    package com.mmall.concurrency.example.atomic;

import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicLong;

/**

  • @author shishushengbr/>*/
    @Slf4j
    @ThreadSafe
    public class AtomicExample2 {

    /**

    • 請求總數
      */
      public static int clientTotal = 5000;

    /**

    • 同時併發執行的線程數
      */
      public static int threadTotal = 200;

    /**

    • 工作內存
      */
      public static AtomicLong count = new AtomicLong(0);

    public static void main(String[] args) throws Exception {
    ExecutorService executorService = Executors.newCachedThreadPool();
    final Semaphore semaphore = new Semaphore(threadTotal);
    final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
    for (int i = 0; i < clientTotal ; i++) {
    executorService.execute(() -> {
    try {
    System.out.println();
    semaphore.acquire();
    add();
    semaphore.release();
    } catch (Exception e) {
    log.error("exception", e);
    }
    countDownLatch.countDown();
    });
    }
    countDownLatch.await();
    executorService.shutdown();
    //主內存
    log.info("count:{}", count.get());
    }

    private static void add() {
    count.incrementAndGet();
    // count.getAndIncrement();
    }
    }

package com.mmall.concurrency.example.atomic;

import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author shishusheng
 * @date 18/4/3
 */
@Slf4j
@ThreadSafe
public class AtomicExample4 {

    private static AtomicReference<Integer> count = new AtomicReference<>(0);

    public static void main(String[] args) {
        // 2
        count.compareAndSet(0, 2);
        // no
        count.compareAndSet(0, 1);
        // no
        count.compareAndSet(1, 3);
        // 4
        count.compareAndSet(2, 4);
        // no
        count.compareAndSet(3, 5); 
        log.info("count:{}", count.get());
    }
}

輸出結果

  • AtomicReference,AtomicReferenceFieldUpdater
    長文慎入-探索Java併發編程與高併發解決方案
  • AtomicBoolean
    長文慎入-探索Java併發編程與高併發解決方案

  • AtomicStampReference : CAS的 ABA 問題
    ###4.2.2 鎖
    synchronized:依賴 JVM
  • 修飾代碼塊:大括號括起來的代碼,作用於調用的對象
  • 修飾方法: 整個方法,作用於調用的對象
    長文慎入-探索Java併發編程與高併發解決方案
  • 修飾靜態方法:整個靜態方法,作用於所有對象
    長文慎入-探索Java併發編程與高併發解決方案
    
    package com.mmall.concurrency.example.count;

import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**

  • @author shishushengbr/>*/
    @Slf4j
    @ThreadSafe
    public class CountExample3 {

    /**

    • 請求總數
      */
      public static int clientTotal = 5000;

    /**

    • 同時併發執行的線程數
      */
      public static int threadTotal = 200;

    public static int count = 0;

    public static void main(String[] args) throws Exception {
    ExecutorService executorService = Executors.newCachedThreadPool();
    final Semaphore semaphore = new Semaphore(threadTotal);
    final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
    for (int i = 0; i < clientTotal ; i++) {
    executorService.execute(() -> {
    try {
    semaphore.acquire();
    add();
    semaphore.release();
    } catch (Exception e) {
    log.error("exception", e);
    }
    countDownLatch.countDown();
    });
    }
    countDownLatch.await();
    executorService.shutdown();
    log.info("count:{}", count);
    }

    private synchronized static void add() {
    count++;
    }
    }

    
    synchronized 修正計數類方法
    - 修飾類:括號括起來的部分,作用於所有對象
    子類繼承父類的被 synchronized 修飾方法時,是沒有 synchronized 修飾的!!!

Lock: 依賴特殊的 CPU 指令,代碼實現
###4.2.3 對比

  • synchronized: 不可中斷鎖,適合競爭不激烈,可讀性好
  • Lock: 可中斷鎖,多樣化同步,競爭激烈時能維持常態
  • Atomic: 競爭激烈時能維持常態,比Lock性能好; 只能同步一
    個值
    ##4.3 可見性
    一個線程對主內存的修改可以及時的被其他線程觀察到
    ###4.3.1 導致共享變量在線程間不可見的原因
  • 線程交叉執行
  • 重排序結合線程交叉執行
  • 共享變量更新後的值沒有在工作內存與主存間及時更新
    ###4.3.2 可見性之synchronized
    JMM關於synchronized的規定
  • 線程解鎖前,必須把共享變量的最新值刷新到主內存
  • 線程加鎖時,將清空工作內存中共享變量的值,從而使
    用共享變量時需要從主內存中重新讀取最新的值(加鎖與解鎖是同一把鎖)
    ###4.3.3 可見性之volatile
    通過加入內存屏障和禁止重排序優化來實現
  • 對volatile變量寫操作時,會在寫操作後加入一條store
    屏障指令,將本地內存中的共享變量值刷新到主內存
  • 對volatile變量讀操作時,會在讀操作前加入一條load
    屏障指令,從主內存中讀取共享變量
    volatile 寫
    volatile 讀
    計數類之 volatile 版,非線程安全的
  • volatile使用
    
    volatile boolean inited = false;

//線程1:
context = loadContext();
inited= true;

// 線程2:
while( !inited ){
sleep();
}
doSomethingWithConfig(context)


##4.4 有序性
一個線程觀察其他線程中的指令執行順序,由於指令重排序的存在,該觀察結果一般雜亂無序

JMM允許編譯器和處理器對指令進行重排序,但是重排序過程不會影響到單線程程序的執行,卻會影響到多線程併發執行的正確性
###4.4.1 happens-before 規則
#5發佈對象
![](https://upload-images.jianshu.io/upload_images/4685968-ed313a1caed24223.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![發佈對象](https://upload-images.jianshu.io/upload_images/4685968-b368f6fe5b350cbe.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![對象逸出](https://upload-images.jianshu.io/upload_images/4685968-88d207fcc6bf1866.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
##5.1 安全發佈對象
![](https://upload-images.jianshu.io/upload_images/4685968-7400ab2abe1dbbfb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![非線程安全的懶漢模式](https://upload-images.jianshu.io/upload_images/4685968-ba18bdbe3a3c4ed1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![餓漢模式](https://upload-images.jianshu.io/upload_images/4685968-be2854c290143094.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![線程安全的懶漢模式](https://upload-images.jianshu.io/upload_images/4685968-e632243a5a97281a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

package com.mmall.concurrency.example.singleton;

import com.mmall.concurrency.annoations.NotThreadSafe;

/**

  • 懶漢模式 -》 雙重同步鎖單例模式
  • 單例實例在第一次使用時進行創建
  • @author shishushengbr/>*/
    @NotThreadSafe
    public class SingletonExample4 {

    /**

    • 私有構造函數
      */
      private SingletonExample4() {

    }

    // 1、memory = allocate() 分配對象的內存空間
    // 2、ctorInstance() 初始化對象
    // 3、instance = memory 設置instance指向剛分配的內存

    // JVM和cpu優化,發生了指令重排

    // 1、memory = allocate() 分配對象的內存空間
    // 3、instance = memory 設置instance指向剛分配的內存
    // 2、ctorInstance() 初始化對象

    /**

    • 單例對象
      */
      private static SingletonExample4 instance = null;

    /**

    • 靜態的工廠方法
    • @return
      */
      public static SingletonExample4 getInstance() {
      // 雙重檢測機制 // B
      if (instance == null) {
      // 同步鎖
      synchronized (SingletonExample4.class) {
      if (instance == null) {
      // A - 3
      instance = new SingletonExample4();
      }
      }
      }
      return instance;
      }
      }
      ![](https://upload-images.jianshu.io/upload_images/4685968-823166cdf7936293.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
      ![](https://upload-images.jianshu.io/upload_images/4685968-9002671b71096f6c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
      #7 AQS
      ##7.1 介紹
      ![數據結構](https://upload-images.jianshu.io/upload_images/4685968-918dcaea77d556e9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
      - 使用Node實現FIFO隊列,可以用於構建鎖或者其他同步裝置的基礎框架
      - 利用了一個int類型表示狀態
      - 使用方法是繼承
      - 子類通過繼承並通過實現它的方法管理其狀態{acquire 和release} 的方法操縱狀態
      - 可以同時實現排它鎖和共享鎖模式(獨佔、共享)
      同步組件
      ###CountDownLatch

      package com.mmall.concurrency.example.aqs;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**

  • @author shishushengbr/>*/
    @Slf4j
    public class CountDownLatchExample1 {

    private final static int threadCount = 200;

    public static void main(String[] args) throws Exception {

    ExecutorService exec = Executors.newCachedThreadPool();
    
    final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
    
    for (int i = 0; i < threadCount; i++) {
        final int threadNum = i;
        exec.execute(() -> {
            try {
                test(threadNum);
            } catch (Exception e) {
                log.error("exception", e);
            } finally {
                countDownLatch.countDown();
            }
        });
    }
    countDownLatch.await();
    log.info("finish");
    exec.shutdown();

    }

    private static void test(int threadNum) throws Exception {
    Thread.sleep(100);
    log.info("{}", threadNum);
    Thread.sleep(100);
    }
    }

    package com.mmall.concurrency.example.aqs;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**

  • 指定時間內處理任務
  • @author shishusheng
  • */br/>@Slf4j
    public class CountDownLatchExample2 {

    private final static int threadCount = 200;

    public static void main(String[] args) throws Exception {

    ExecutorService exec = Executors.newCachedThreadPool();
    
    final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
    
    for (int i = 0; i < threadCount; i++) {
        final int threadNum = i;
        exec.execute(() -> {
            try {
                test(threadNum);
            } catch (Exception e) {
                log.error("exception", e);
            } finally {
                countDownLatch.countDown();
            }
        });
    }
    countDownLatch.await(10, TimeUnit.MILLISECONDS);
    log.info("finish");
    exec.shutdown();

    }

    private static void test(int threadNum) throws Exception {
    Thread.sleep(100);
    log.info("{}", threadNum);
    }
    }

    ##Semaphore用法
    ![](https://upload-images.jianshu.io/upload_images/4685968-e6cbcd4254c642c5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    ![](https://upload-images.jianshu.io/upload_images/4685968-dbefbf2c76ad5a2a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    ![](https://upload-images.jianshu.io/upload_images/4685968-41f5f5a5fd135804.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    ##CycliBarrier

    package com.mmall.concurrency.example.aqs;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**

  • @author shishushengbr/>*/
    @Slf4j
    public class CyclicBarrierExample1 {

    private static CyclicBarrier barrier = new CyclicBarrier(5);

    public static void main(String[] args) throws Exception {

    ExecutorService executor = Executors.newCachedThreadPool();
    
    for (int i = 0; i < 10; i++) {
        final int threadNum = i;
        Thread.sleep(1000);
        executor.execute(() -> {
            try {
                race(threadNum);
            } catch (Exception e) {
                log.error("exception", e);
            }
        });
    }
    executor.shutdown();

    }

    private static void race(int threadNum) throws Exception {
    Thread.sleep(1000);
    log.info("{} is ready", threadNum);
    barrier.await();
    log.info("{} continue", threadNum);
    }
    }

    ![](https://upload-images.jianshu.io/upload_images/4685968-4fb51fa4926fd70e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

    package com.mmall.concurrency.example.aqs;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**

  • @author shishushengbr/>*/
    @Slf4j
    public class CyclicBarrierExample2 {

    private static CyclicBarrier barrier = new CyclicBarrier(5);

    public static void main(String[] args) throws Exception {

    ExecutorService executor = Executors.newCachedThreadPool();
    
    for (int i = 0; i < 10; i++) {
        final int threadNum = i;
        Thread.sleep(1000);
        executor.execute(() -> {
            try {
                race(threadNum);
            } catch (Exception e) {
                log.error("exception", e);
            }
        });
    }
    executor.shutdown();

    }

    private static void race(int threadNum) throws Exception {
    Thread.sleep(1000);
    log.info("{} is ready", threadNum);
    try {
    barrier.await(2000, TimeUnit.MILLISECONDS);
    } catch (Exception e) {
    log.warn("BarrierException", e);
    }
    log.info("{} continue", threadNum);
    }
    }

    ![await 超時導致程序拋異常](https://upload-images.jianshu.io/upload_images/4685968-0f899c23531f8ee8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

    package com.mmall.concurrency.example.aqs;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**

  • @author shishushengbr/>*/
    @Slf4j
    public class SemaphoreExample3 {

    private final static int threadCount = 20;

    public static void main(String[] args) throws Exception {

    ExecutorService exec = Executors.newCachedThreadPool();
    
    final Semaphore semaphore = new Semaphore(3);
    
    for (int i = 0; i < threadCount; i++) {
        final int threadNum = i;
        exec.execute(() -> {
            try {
                // 嘗試獲取一個許可
                if (semaphore.tryAcquire()) {
                    test(threadNum);
                    // 釋放一個許可
                    semaphore.release();
                }
            } catch (Exception e) {
                log.error("exception", e);
            }
        });
    }
    exec.shutdown();

    }

    private static void test(int threadNum) throws Exception {
    log.info("{}", threadNum);
    Thread.sleep(1000);
    }

}


#9 線程池
##9.1 newCachedThreadPool
![](https://upload-images.jianshu.io/upload_images/4685968-1122da7a48223ba1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
##9.2 newFixedThreadPool
![](https://upload-images.jianshu.io/upload_images/4685968-0ea942bf12e5210f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
##9.3 newSingleThreadExecutor
看出是順序執行的
![](https://upload-images.jianshu.io/upload_images/4685968-989d59429f589403.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
##9.4 newScheduledThreadPool
![](https://upload-images.jianshu.io/upload_images/4685968-f7536ec7a1cf6ecc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://upload-images.jianshu.io/upload_images/4685968-c90e09d5bfe707e6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
#10 死鎖
![](https://upload-images.jianshu.io/upload_images/4685968-461f6a4251ae8ca4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://upload-images.jianshu.io/upload_images/4685968-46d58773e597195f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章