Java并發(fā)編程

第1章 課程準(zhǔn)備

本章首先從課程重點(diǎn)拇囊、特點(diǎn)圣猎、適合人群及學(xué)習(xí)收獲幾個方面對課程進(jìn)行整體的介紹逝钥,然后會從一個實(shí)際的計數(shù)場景實(shí)現(xiàn)開始,給大家展示多線程并發(fā)時的線程不安全問題糕再,讓大家能夠初體驗(yàn)到并發(fā)編程量没,之后會講解并發(fā)和高并發(fā)的概念,并通過對比讓大家明白到底什么是并發(fā)和高并發(fā)亿鲜,最后會給出課程涉及到的知識技能允蜈,為后續(xù)的學(xué)習(xí)做好準(zhǔn)備。

1-1 課程導(dǎo)學(xué)

  • 高并發(fā)解決思路與手段


    3.jpg

1-2 并發(fā)編程初體驗(yàn)

多線程計數(shù)不準(zhǔn)。

1-3 并發(fā)與高并發(fā)基本概念

第2章 并發(fā)基礎(chǔ)

本章主要講解并發(fā)學(xué)習(xí)必須理解的一些基本概念价淌,主要包括CPU多級緩存和Java內(nèi)存模型(JMM)肴焊。其中CPU多級緩存里深入講解了緩存一致性和亂序執(zhí)行優(yōu)化。Java內(nèi)存模型(JMM)里詳細(xì)講解了JMM規(guī)定、JMM抽象結(jié)構(gòu)巾陕、同步的八種操作及同步規(guī)則向叉。這些基本概念對于后面的并發(fā)編程很重要,也屬于面試程ピ矗考點(diǎn),需要認(rèn)真體會掌握熏挎。

2-1 CPU多級緩存-緩存一致性

  • CPU的多級緩存
    [圖片上傳中...(image.png-8a8b17-1529669416185-0)]


    image.png
  • 為什么需要CPU緩沖
    cpu的頻率太快聋溜,快到主存跟不上谆膳,是為了緩解CPU的內(nèi)存之間的速度不匹配的問題。
  • 緩存一致性(MESI)
    用于保證多個CPU cache之間緩存共享數(shù)據(jù)的一致性


    image.png

M: 被修改(Modified)

該緩存行只被緩存在該CPU的緩存中撮躁,并且是被修改過的(dirty)漱病,即與主存中的數(shù)據(jù)不一致,該緩存行中的內(nèi)存需要在未來的某個時間點(diǎn)(允許其它CPU讀取請主存中相應(yīng)內(nèi)存之前)寫回(write back)主存把曼。當(dāng)被寫回主存之后褐鸥,該緩存行的狀態(tài)會變成獨(dú)享(exclusive)狀態(tài)鸿捧。

E: 獨(dú)享的(Exclusive)

該緩存行只被緩存在該CPU的緩存中坷衍,它是未被修改過的(clean)馍佑,與主存中數(shù)據(jù)一致。該狀態(tài)可以在任何時刻當(dāng)有其它CPU讀取該內(nèi)存時變成共享狀態(tài)(shared)叙赚。同樣地老客,當(dāng)CPU修改該緩存行中內(nèi)容時,該狀態(tài)可以變成Modified狀態(tài)震叮。

S: 共享的(Shared)

該狀態(tài)意味著該緩存行可能被多個CPU緩存胧砰,并且各個緩存中的數(shù)據(jù)與主存數(shù)據(jù)一致(clean),當(dāng)有一個CPU修改該緩存行中苇瓣,其它CPU中該緩存行可以被作廢(變成無效狀態(tài)(Invalid))朴则。

I: 無效的(Invalid)
該緩存是無效的(可能有其它CPU修改了該緩存行)。

2-2 CPU多級緩存-亂序執(zhí)行優(yōu)化

  • 處理器為類提高運(yùn)算速度而做出違背代碼原有順序的優(yōu)化


    image.png

    在單核的條件下钓简,沒問題,但在多核執(zhí)行的情況下汹想,每個核都可能亂序外邓。

2-3 JAVA內(nèi)存模型(Java Memory Model, JMM)

  • 基本模型


    5.jpg

Stack: 存放的數(shù)據(jù)大小與生存期是確定的,缺乏靈活性古掏,速度比較快损话,數(shù)據(jù)可已共享,存放基本類型和句柄
Heap: 運(yùn)行時確定大小,生存期也不必事先告訴編譯器丧枪,是在運(yùn)行期在動態(tài)分配內(nèi)存的光涂,垃圾收集器會清理它們,由于動態(tài)分配的內(nèi)存拧烦,速度比較慢

  • cpu寄存器


    image.png
  • 對比


    image.png
  • 抽象結(jié)果圖


    image.png

線程A把本地內(nèi)存中的共享變量副本刷新到主內(nèi)存里忘闻,線程B從主內(nèi)存中讀取。

  • 同步操作與規(guī)則


    image.png

2-4 并發(fā)的優(yōu)勢與風(fēng)險

10.jpg

第3章 項(xiàng)目準(zhǔn)備

本章主要是為課程里代碼演示做必要的準(zhǔn)備恋博。首先會基于SpringBoot快速搭建一個方便演示的Java項(xiàng)目齐佳,然后簡單介紹一下碼云及代碼的管理。項(xiàng)目搭建好债沮,我會使用簡單的例子演示一下并發(fā)的模擬驗(yàn)證炼吴,主要包括對工具Postman、JMeter疫衩、Apache Bench(AB)的使用硅蹦,以及使用并發(fā)的代碼來驗(yàn)證并發(fā)處理的正確性。

3-1 案例環(huán)境初始化

https://blog.csdn.net/lom9357bye/article/details/69677120
log:@Slf4j
https://www.2cto.com/kf/201712/702543.html

3-2 案例準(zhǔn)備工作

3-3 并發(fā)模擬-工具

3-4 并發(fā)模擬-代碼

第4章 線程安全性

本章講解線程安全性闷煤,主要從原子性童芹、可見性、有序性三個方面進(jìn)行講解曹傀。原子性部分辐脖,會詳細(xì)講解atomic包下相關(guān)類、CAS原理皆愉、Unsafe類嗜价、synchronized關(guān)鍵字等的使用及注意事項(xiàng)∧宦可見性部分久锥,主要介紹的是volatile關(guān)鍵字的規(guī)則和使用,及synchronized關(guān)鍵字的可見性异剥。有序性部分瑟由,則重點(diǎn)講解了happens-before原則。

4-1 線程安全性-原子性-atomic-1

image.png
image.png
  • 基本類


    image.png
  • AtomicInteger

@Slf4j
@ThreadSafe
public class CountExample2 {

    // 請求總數(shù)
    public static int clientTotal = 5000;

    // 同時并發(fā)執(zhí)行的線程數(shù)
    public static int threadTotal = 200;

    public static AtomicInteger count = new AtomicInteger(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.get());
    }

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

使用了AtomicInteger這個類冤寿,可以保證原子性歹苦。當(dāng)我們count.incrementAndGet();的時候,底層使用了這樣的代碼

    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

基于CAS:Compare and Swap督怜, 翻譯成比較并交換

CAS有3個操作數(shù)殴瘦,內(nèi)存值V,舊的預(yù)期值A(chǔ)号杠,要修改的新值B蚪腋。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時丰歌,將內(nèi)存值V修改為B,否則什么都不做屉凯。

詳情看如下博客

  • LongAdder
public class AtomicExample3 {

    // 請求總數(shù)
    public static int clientTotal = 5000;

    // 同時并發(fā)執(zhí)行的線程數(shù)
    public static int threadTotal = 200;

    public static LongAdder count = new LongAdder();

    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.increment();
    }
}
  • AtomicLong和LongAdder對比
  • AtomicReference立帖、AtomicReferneceFieldUpdater
    這兩個用的比較少
  • AtomicStampReference:CAS的ABA問題
    ABA問題就是其他線程把A值改為B又改回A,CAS獲得值與期望值相等悠砚,這是就需要再添加一個版本號來控制晓勇。

4-3 線程安全性-原子性-synchronized

image.png
  • 修飾代碼塊:大括號括起來的代碼,作用于調(diào)用的對象
@Slf4j
public class SynchronizedExample1 {

    // 修飾一個代碼塊
    public void test1() {
      synchronized (this) {
          for (int i = 0; i < 10; i++) {
              log.info("test1 - {}", i);
          }
      }
    }

    // 修飾一個方法
    public synchronized void test2() {
        for (int i = 0; i < 10; i++) {
            log.info("test - {}", i);
        }
    }

    public static void main(String[] args) {
        // 聲明一個實(shí)例
        SynchronizedExample1 example1 = new SynchronizedExample1();
        // 聲明一個線程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(()->{
            example1.test1();
        });

        executorService.execute(()->{
            example1.test1();
        });
    }
}

先打印test1-0到9再打印test1-0到9

15:22:05.516 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 0
15:22:05.526 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 1
15:22:05.526 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 2
15:22:05.526 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 3
15:22:05.526 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 4
15:22:05.527 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 5
15:22:05.527 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 6
15:22:05.527 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 7
15:22:05.527 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 8
15:22:05.527 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 9
15:22:05.527 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 0
15:22:05.527 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 1
15:22:05.527 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 2
15:22:05.527 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 3
15:22:05.527 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 4
15:22:05.527 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 5
15:22:05.527 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 6
15:22:05.527 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 7
15:22:05.527 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 8
15:22:05.527 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 9

使用線程池哩簿,他們本來可以同時進(jìn)行的宵蕉,加了synchronized 就同步是使用了同一個對象。

當(dāng)使用多了對象時

@Slf4j
public class SynchronizedExample1 {

    // 修飾一個代碼塊
    public void test1() {
      synchronized (this) {
          for (int i = 0; i < 10; i++) {
              log.info("test1 - {}", i);
          }
      }
    }

    // 修飾一個方法
    public synchronized void test2() {
        for (int i = 0; i < 10; i++) {
            log.info("test - {}", i);
        }
    }

    public static void main(String[] args) {
        // 聲明一個實(shí)例
        SynchronizedExample1 example1 = new SynchronizedExample1();
        SynchronizedExample1 example2 = new SynchronizedExample1();
        // 聲明一個線程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(()->{
            example1.test1();
        });

        executorService.execute(()->{
            example2.test1();
        });
    }
}

打印結(jié)果节榜,是交替出現(xiàn)的羡玛,也不一定是交替,不同對象調(diào)用時互相不影響的

15:35:12.188 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 0
15:35:12.188 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 0
15:35:12.196 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 1
15:35:12.196 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 1
15:35:12.196 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 2
15:35:12.196 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 2
15:35:12.196 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 3
15:35:12.196 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 3
15:35:12.196 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 4
15:35:12.196 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 4
15:35:12.196 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 5
15:35:12.196 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 5
15:35:12.197 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 6
15:35:12.197 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 6
15:35:12.197 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 7
15:35:12.197 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 7
15:35:12.197 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 8
15:35:12.197 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 8
15:35:12.197 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 9
15:35:12.197 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample1 - test1 - 9
  • 修飾方法:整個方法宗苍,作用于調(diào)用的對象
    與上面類似
  • 修飾靜態(tài)方法:整個靜態(tài)方法稼稿,作用于所有對象
public class SynchronizedExample2 {

    // 修飾一個類
    public static void test1(int j) {
      synchronized (SynchronizedExample2.class) {
          for (int i = 0; i < 10; i++) {
              log.info("test1 -{} - {}",j, i);
          }
      }
    }

    // 修飾一個靜態(tài)方法
    public static synchronized void test2(int j) {
        for (int i = 0; i < 10; i++) {
            log.info("test2 - {} - {}", i);
        }
    }

    public static void main(String[] args) {
        // 聲明一個實(shí)例
        SynchronizedExample2 example1 = new SynchronizedExample2();
        SynchronizedExample2 example2 = new SynchronizedExample2();
        // 聲明一個線程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(()->{
            example1.test1(1);
        });

        executorService.execute(()->{
            example2.test1(2);
        });
    }
}

使用不同的對象,打印出來讳窟,是先打印test1 -1 線程的让歼,再打印test1 - 2這個線程

15:46:20.320 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample2 - test1 -1 - 0
15:46:20.330 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample2 - test1 -1 - 1
15:46:20.330 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample2 - test1 -1 - 2
15:46:20.330 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample2 - test1 -1 - 3
15:46:20.330 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample2 - test1 -1 - 4
15:46:20.330 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample2 - test1 -1 - 5
15:46:20.330 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample2 - test1 -1 - 6
15:46:20.330 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample2 - test1 -1 - 7
15:46:20.330 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample2 - test1 -1 - 8
15:46:20.330 [pool-1-thread-1] INFO com.cuzz.concurrency.example.sync.SynchronizedExample2 - test1 -1 - 9
15:46:20.330 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample2 - test1 -2 - 0
15:46:20.330 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample2 - test1 -2 - 1
15:46:20.330 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample2 - test1 -2 - 2
15:46:20.330 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample2 - test1 -2 - 3
15:46:20.330 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample2 - test1 -2 - 4
15:46:20.330 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample2 - test1 -2 - 5
15:46:20.330 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample2 - test1 -2 - 6
15:46:20.330 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample2 - test1 -2 - 7
15:46:20.331 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample2 - test1 -2 - 8
15:46:20.331 [pool-1-thread-2] INFO com.cuzz.concurrency.example.sync.SynchronizedExample2 - test1 -2 - 9

  • 修飾類:括號括起來的部分,作用于所有對象
    與上面一致
  • 計數(shù)
    在add方法上添加一個synchronized關(guān)鍵字
@Slf4j
@ThreadSafe
public class CountExample3 {

    // 請求總數(shù)
    public static int clientTotal = 5000;

    // 同時并發(fā)執(zhí)行的線程數(shù)
    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++;
    }
}

輸出結(jié)果

15:52:21.184 [main] INFO com.cuzz.concurrency.example.count.CountExample3 - count:500
  • 對比


    image.png

4-4 線程安全性-可見性

  • 導(dǎo)致共享變量在線程間不可見的原因


    image.png
  • JMM關(guān)于synchronized的兩天規(guī)定


    image.png
  • volatile


    image.png
  • volatile寫


    image.png
  • volatile讀


    image.png
  • 計數(shù)
    使用volatile關(guān)鍵字丽啡,也不是線程安全的

當(dāng)我count++時
分為3步 1. 從主存中讀取count 2. +1 3. 把count寫回主存
當(dāng)兩個兩個線程同時獲取時谋右,雖然它們都是讀的最新的值,但是有時候會少計數(shù)

@Slf4j
@NotThreadSafe
public class CountExample4 {

    // 請求總數(shù)
    public static int clientTotal = 5000;

    // 同時并發(fā)執(zhí)行的線程數(shù)
    public static int threadTotal = 200;

    public static volatile 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++;
    }
}
  • volatile使用


    image.png

4-5 線程安全性-有序性

  • 有序性


    image.png
  • happens-before原則


    image.png

    image.png

    image.png

    image.png

4-6 線程安全性總結(jié)

image.png

第5章 安全發(fā)布對象

本章主要講解安全發(fā)布對象的一些核心方法补箍,主要通過單例類的多種實(shí)現(xiàn)方式改执,讓大家在實(shí)現(xiàn)過程中去體會這些方法的具體含義。這一章也是對線程安全性的鞏固坑雅,也是把線程安全性涉及的一些關(guān)鍵字和類再一次放到實(shí)際場景中使用辈挂,加深大家對他們的印象和認(rèn)識。

5-1 安全發(fā)布對象-發(fā)布與逸出

image.png
  • 不安全
    這是一個不安全的類裹粤,當(dāng)運(yùn)行時终蒂,任何線程都可修改這個類的屬性,重合改變狀態(tài)
@Slf4j
public class UnsafePublic {
    private String[] states = {"a", "b", "c"};

    public String[] getStrates() {
        return states;
    }

    public static void main(String[] args) {
        UnsafePublic unsafePublic = new UnsafePublic();
        log.info("{}", Arrays.toString(unsafePublic.getStrates()));

        unsafePublic.getStrates()[0] = "d";
        log.info("{}", Arrays.toString(unsafePublic.getStrates()));
    }
}
  • 逸出
    對象沒有被正常構(gòu)造完全之前遥诉,它就會被發(fā)布拇泣,在對象還沒完成之前,不能將其發(fā)布矮锈。
@Slf4j
public class Escape {

    private int thisCanBeEscape = 0;

    public Escape() {
        new InnerClass();
    }

    private class InnerClass{

        public InnerClass() {
            log.info("{}", Escape.this.thisCanBeEscape);
        }
    }

    public static void main(String[] args) {
        new Escape();
    }
}

5-2 安全發(fā)布對象-四種方法

如果一個對象時可變對象霉翔,就要安全的發(fā)布。


image.png
  • 在靜態(tài)初始化函數(shù)中初始化一個對象的引用
  • 懶漢模式
    在單線程運(yùn)行下沒有問題愕难,但是在多線程下會出現(xiàn)問題,當(dāng)兩個線程都執(zhí)行到 if (instance == null)時候都會去創(chuàng)建一個新的對象。
/**
 * 懶漢模式
 * 單例實(shí)例在第一次使用的時候創(chuàng)建出來
 */
@NotThreadSafe
public class SingletonExample1 {

    // 私有的構(gòu)造函數(shù)
    private SingletonExample1() {

    }

    // 單例對象
    private static SingletonExample1 instance = null;

    // 靜態(tài)的工廠方法
    public static SingletonExample1 getInstance() {
        if (instance == null) {
            instance = new SingletonExample1();
        }
        return instance;
    }
}
  • 餓漢模式
    可能會使類加載非常慢猫缭,可能引起性能問題葱弟,如果只加載而不調(diào)用,會產(chǎn)生資源的浪費(fèi)
    使用餓漢模式猜丹,要保證2點(diǎn)芝加,第一是類加載比較簡單,第二是這個類肯定會被使用
/**
 * 餓漢模式
 * 單例實(shí)例在類裝載使用時候創(chuàng)建出來
 */
@ThreadSafe
public class SingletonExample2 {

    // 私有的構(gòu)造函數(shù)
    private SingletonExample2() {

    }

    // 單例對象
    private static SingletonExample2 instance = new SingletonExample2();

    // 靜態(tài)的工廠方法
    public static SingletonExample2 getInstance() {
        return instance;
    }
}
  • 懶漢模式(線程安全的-不推薦)
    帶來性能的開銷
/**
 * 懶漢模式
 * 單例實(shí)例在第一次使用的時候創(chuàng)建出來
 */
@ThreadSafe
@NotRecommend
public class SingletonExample3 {

    // 私有的構(gòu)造函數(shù)
    private SingletonExample3() {

    }

    // 單例對象
    private static SingletonExample3 instance = null;

    // 靜態(tài)的工廠方法
    public static synchronized  SingletonExample3 getInstance() {
        if (instance == null) {
            instance = new SingletonExample3();
        }
        return instance;
    }
}
  • 懶漢模式(不是線程安全的-雙重同步鎖)
    // 1. memory = allocate() 分配對象空間
    // 2. ctorInstance() 初始化對象
    // 3. instance = memory 設(shè)置instance指向剛分配的內(nèi)存

    // JVM和cpu優(yōu)化射窒,發(fā)生了指令重排

    // 1. memory = allocate() 分配對象空間
    // 3. instance = memory 設(shè)置instance指向剛分配的內(nèi)存
    // 2. ctorInstance() 初始化對象
    指令重排藏杖,如果線程B在判斷時候已經(jīng)發(fā)現(xiàn)分配空間,就返回了instance脉顿,其實(shí)A還沒有初始化對象蝌麸。

/**
 * 懶漢模式 雙重鎖同步單例模式
 * 單例實(shí)例在第一次使用的時候創(chuàng)建出來
 */
@NotThreadSafe
public class SingletonExample4 {

    // 私有的構(gòu)造函數(shù)
    private SingletonExample4() {

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

    // 靜態(tài)的工廠方法
    public static SingletonExample4 getInstance() {
        if (instance == null) { // 雙重檢測機(jī)制        // B
            synchronized (SingletonExample4.class) { // 同步鎖  // A - 3
               if (instance == null) {
                   instance = new SingletonExample4();
               }
            }
        }
        return instance;
    }
}
  • 懶漢模式(線程安全的-雙重同步鎖-volatile)
    volatile禁止指令重排
/**
 * 懶漢模式 雙重鎖同步單例模式
 * 單例實(shí)例在第一次使用的時候創(chuàng)建出來
 */
@ThreadSafe
public class SingletonExample5 {

    // 私有的構(gòu)造函數(shù)
    private SingletonExample5() {

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

    // JVM和cpu優(yōu)化,發(fā)生了指令重排

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

    // 單例對象 // 加上volatile防止指令重排
    private volatile static  SingletonExample5 instance = null;

    // 靜態(tài)的工廠方法
    public static SingletonExample5 getInstance() {
        if (instance == null) { // 雙重檢測機(jī)制
            synchronized (SingletonExample5.class) { // 同步鎖
               if (instance == null) {
                   instance = new SingletonExample5();
               }
            }
        }
        return instance;
    }
}
  • 餓漢模式(注意)
    靜態(tài)域一定要再靜態(tài)代碼塊之前艾疟,因?yàn)樗麄兪前凑昭驁?zhí)行的来吩,否者會報空指針異常
/**
 * 餓漢模式
 * 單例實(shí)例在類裝載使用時候創(chuàng)建出來
 */
@ThreadSafe
public class SingletonExample6 {

    // 私有的構(gòu)造函數(shù)
    private SingletonExample6() {

    }
    
    // 單例對象
    // 靜態(tài)域一定要再靜態(tài)代碼塊之前,因?yàn)樗麄兪前凑昭驁?zhí)行的
    private static SingletonExample6 instance = null;
    
    static {
        instance = new SingletonExample6();
    }


    // 靜態(tài)的工廠方法
    public static SingletonExample6 getInstance() {
        return instance;
    }
}
  • 枚舉(線程安全的-推薦)
/**
 * 枚舉模式:最安全的
 */
@ThreadSafe
@Recommend
public class SingletonExample7 {
    // 私有構(gòu)造函數(shù)
    private SingletonExample7() {

    }

    public static SingletonExample7 getInstance() {
        return Singleton.INSTANCE.getInstance();

    }

    private enum Singleton {
        INSTANCE;

        private SingletonExample7 singleton;

        // JVM保證這個方法絕對只調(diào)用一次
        Singleton() {
            singleton = new SingletonExample7();
        }

        public SingletonExample7 getInstance() {
            return singleton;
        }
    }
}

第6章 線程安全策略

本章主要講解線程安全策略蔽莱,包括定義不可變對象弟疆、線程封閉、同步容器盗冷、并發(fā)容器等怠苔,引出并發(fā)里的關(guān)鍵知識J.U.C。同時還額外介紹了開發(fā)中常見的一些線程不安全類和寫法仪糖,并給出他們各自對應(yīng)的替代方案柑司。這一章涉及的內(nèi)容在日常開發(fā)和面試中都會涉及很多。

6-1 不可變對象-1

  • 不可變對象需要的滿足條件


    image.png
  • final關(guān)鍵字


    image.png
public class ImmutableExample1 {

    private final static Integer a = 1;
    private final static String b = "2";
    private final static Map<Integer, Integer> map = new HashMap<>();

    static {
        map.put(1, 2);
        map.put(3, 4);
        map.put(5, 6);
    }
    
    // 可以保證變量不發(fā)生變化
    private void test(final int a) {
        // a = 1;  // 編譯出錯
    }

    public static void main(String[] args) {
        // a = 2;   // 編譯出錯
        // b = "3"; // 編譯出錯
        // map = new HashMap<>(); // 編譯出錯
        map.put(1, 3);   // 引用對象可以修改里面的值
    }
}

6-2 不可變對象-2

image.png
@ThreadSafe
public class ImmutableExample2 {
    private static Map<Integer, Integer> map = new HashMap<>();

    static {
        map.put(1, 2);
        map.put(3, 4);
        map.put(5, 6);
        map = Collections.unmodifiableMap(map);
    }

    public static void main(String[] args) {
        map.put(1, 3); // 運(yùn)行時報錯
    }
}

6-3 線程封閉-1

image.png

還不是很清楚乓诽,可以看看以下博客
ThreadLocal

6-4 線程封閉-2

6-5 線程不安全類與寫法-1

  • 先檢查再執(zhí)行:if(condition(a)){handle(a)}帜羊,這樣的很容易引起線程不安全
  • StringBuilder -> StringBuffer
    StringBuffer加了synchronized關(guān)鍵字同步,影響性能
  • SimpleDataFormat -> JodaTime
  • ArrayList, HashSet, HashMap等Collections

6-6 線程不安全類與寫法-2

6-7 同步容器-1

  • ArrayList -> Vector, Stack
  • HashMap -> HashTable(key value 不能為null)
  • Collections.synchronizedXXX(List Set Map)

6-8 同步容器-2

在使用foreach和iterator的時候不要刪除元素鸠天。
同步容器使用synchronized性能不好讼育,同步容器也不是完全線程安全的。

6-9 并發(fā)容器及安全共享策略總結(jié)

  • ArrayList -> CopyOnWriteArrayList
    寫操作是需要拷貝數(shù)組稠集,消耗內(nèi)存奶段,不能實(shí)時一致性
  • HashSet TreeSet -> CopyOnWriteArraySet ConcurrentSkipListSet
  • HashMap TreeMap -> ConcurrentHashMap ConcurrentSkioListMap


    image.png

    image.png

第7章 J.U.C之AQS

AQS(AbstractQueuedSynchronizer)是J.U.C的重要組件,也是面試的重要考點(diǎn)剥纷。這一章里將重點(diǎn)講解AQS模型設(shè)計及相關(guān)同步組件的原理和使用痹籍,都非常實(shí)用,具體包括:CountDownLatch晦鞋、Semaphore蹲缠、CyclicBarrier棺克、ReentrantLock與鎖、Condition等线定。這些組件需要大家能熟練明白他們的用途及差異娜谊,不但會使用,而且還要明確知道不同方法調(diào)用后的不同效果斤讥。

7-1 J.U.C之AQS-介紹

image.png

image.png
image.png

image.png

大致思路:首先AQS內(nèi)部維護(hù)了一個CHL隊(duì)列來管理鎖纱皆,線程會首選嘗試獲取鎖,如果獲取失敗芭商,就把當(dāng)前線程信息包裝成一個節(jié)點(diǎn)鎖派草,加入到隊(duì)列中。然后循環(huán)嘗試獲取鎖铛楣,當(dāng)持有鎖釋放鎖時近迁,會喚醒后繼鎖。

7-2 J.U.C之AQS-CountDownLatch

image.png
  • 程序執(zhí)行需要等待某個條件后才執(zhí)行
    所有線程完成之后才會輸出finished
public class CountDownLatchExample1 {

    private final static int threadCount = 200;

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

        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("finished.");
        exec.shutdown();

    }

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

其中的countDownLathc.await(), 還可以接受2個參數(shù):時間和單位蛉艾。其含義是超過這個時間就不管了钳踊。繼續(xù)執(zhí)行下面的代碼。

7-3 J.U.C之AQS-Semaphore

計數(shù)信號量
有限訪問的資源
對并發(fā)訪問的控制

public class SemaphoreExample1 {

    private final static int threadCount = 200;

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

        ExecutorService  exec = Executors.newCachedThreadPool();

        // 并發(fā)量為20
        final Semaphore semaphore = new Semaphore(20);

        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            exec.execute(() ->{
                try {
                    semaphore.acquire(); // 獲取一個許可
                    test(threadNum);
                    semaphore.release(); // 釋放許可
                } catch (Exception e) {
                    log.error("exception", e);
                }
            });
        }
        exec.shutdown();

    }

    private static void test(int threadNum) throws InterruptedException {
        log.info("{}", threadNum);
        Thread.sleep(1000);
    }
}
  • tryAcquire()
public class SemaphoreExample2 {

    private final static int threadCount = 200;

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

        ExecutorService  exec = Executors.newCachedThreadPool();

        // 并發(fā)量為20
        final Semaphore semaphore = new Semaphore(20);

        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 InterruptedException {
        log.info("{}", threadNum);
        Thread.sleep(1000);
    }
}

7-4 J.U.C之AQS-CyclicBarrier

image.png

告訴我們有多少個值同步等待勿侯。

public class CyclicBarrierExample1 {

    private static CyclicBarrier barrier = new CyclicBarrier(5);


    public static void main(String[] args) throws InterruptedException {
        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);
                }

            });
        }
    }

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

7-5 J.U.C之AQS-ReentrantLock與鎖-1

public class LockExample1 {

    // 請求總數(shù)
    public static int clientTotal = 5000;

    // 同時并發(fā)執(zhí)行的線程數(shù)
    public static int threadTotal = 200;

    public static int count = 0;

    private final static Lock lock = new ReentrantLock();

    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() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
}
  • Condition
public class LockExample4 {

    public static void main(String[] args) {
        ReentrantLock reentrantLock = new ReentrantLock();
        Condition condition = reentrantLock.newCondition();

        new Thread(() -> {
            try {
                reentrantLock.lock();
                log.info("wait signal"); // 1
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("get signal"); // 4
            reentrantLock.unlock();
        }).start();

        new Thread(() -> {
            reentrantLock.lock();
            log.info("get lock"); // 2
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            condition.signalAll();
            log.info("send signal ~ "); // 3
            reentrantLock.unlock();
        }).start();
    }
}

7-6 J.U.C之AQS-ReentrantLock與鎖-2

  • ReentrantReadWriteLock 悲觀讀寫鎖
public class LockExample2 {

    private final Map<String, Data> map = new TreeMap<>();

    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    private final Lock readLock = lock.readLock();

    private final Lock writeLock = lock.writeLock();

    public Data get(String key) {
        readLock.lock();
        try {
            return map.get(key);
        } finally {
            readLock.unlock();
        }
    }

    public Set<String> getAllKeys() {
        readLock.lock();
        try {
            return map.keySet();
        } finally {
            readLock.unlock();
        }
    }

    public Data put(String key, Data value) {
        writeLock.lock();
        try {
            return map.put(key, value);
        } finally {
            readLock.unlock();
        }
    }

    class Data {

    }
}
  • StampleLock
    樂觀鎖
public class LockExample3 {

    class Point {
        private double x, y;
        private final StampedLock sl = new StampedLock();

        void move(double deltaX, double deltaY) { // an exclusively locked method
            long stamp = sl.writeLock();
            try {
                x += deltaX;
                y += deltaY;
            } finally {
                sl.unlockWrite(stamp);
            }
        }

        //下面看看樂觀讀鎖案例
        double distanceFromOrigin() { // A read-only method
            long stamp = sl.tryOptimisticRead(); //獲得一個樂觀讀鎖
            double currentX = x, currentY = y;  //將兩個字段讀入本地局部變量
            if (!sl.validate(stamp)) { //檢查發(fā)出樂觀讀鎖后同時是否有其他寫鎖發(fā)生拓瞪?
                stamp = sl.readLock();  //如果沒有,我們再次獲得一個讀悲觀鎖
                try {
                    currentX = x; // 將兩個字段讀入本地局部變量
                    currentY = y; // 將兩個字段讀入本地局部變量
                } finally {
                    sl.unlockRead(stamp);
                }
            }
            return Math.sqrt(currentX * currentX + currentY * currentY);
        }

        //下面是悲觀讀鎖案例
        void moveIfAtOrigin(double newX, double newY) { // upgrade
            // Could instead start with optimistic, not read mode
            long stamp = sl.readLock();
            try {
                while (x == 0.0 && y == 0.0) { //循環(huán)助琐,檢查當(dāng)前狀態(tài)是否符合
                    long ws = sl.tryConvertToWriteLock(stamp); //將讀鎖轉(zhuǎn)為寫鎖
                    if (ws != 0L) { //這是確認(rèn)轉(zhuǎn)為寫鎖是否成功
                        stamp = ws; //如果成功 替換票據(jù)
                        x = newX; //進(jìn)行狀態(tài)改變
                        y = newY;  //進(jìn)行狀態(tài)改變
                        break;
                    } else { //如果不能成功轉(zhuǎn)換為寫鎖
                        sl.unlockRead(stamp);  //我們顯式釋放讀鎖
                        stamp = sl.writeLock();  //顯式直接進(jìn)行寫鎖 然后再通過循環(huán)再試
                    }
                }
            } finally {
                sl.unlock(stamp); //釋放讀鎖或?qū)戞i
            }
        }
    }
}

第8章 J.U.C組件拓展

這一章繼續(xù)講解J.U.C相關(guān)組件祭埂,主要包括FutureTask、Fork/Join框架兵钮、BlockingQueue蛆橡,其中FutureTask講解時會對比著Callable、Runnable掘譬、Future來講泰演。這些組件使用場景相對AQS會少一些,但也是J.U.C的重要組成部分葱轩,也是需要掌握的睦焕。


image.png

8-1 J.U.C-FutureTask-1

  • Future
@Slf4j
public class FutureExample {
    static class MyCallable implements Callable<String> {


        @Override
        public String call() throws Exception {
            log.info("do something in callable");
            sleep(5000);
            return "Done";
        }

    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newSingleThreadExecutor();

        Future<String> future = executor.submit(new MyCallable());
        log.info("do something in main");
        try {
            sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String res = future.get();
        log.info("res: {}", res);

    }
}

8-2 J.U.C-FutureTask-2

  • FutureTask
@Slf4j
public class FutureTaskExample {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                log.info("do something in callable");
                sleep(5000);
                return "Done";
            }
        });

        new Thread(futureTask).start();
        log.info("do something in main");
        try {
            sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String res = futureTask.get();
        log.info("res: {}", res);

    }
}

8-3 J.U.C-ForkJoin


用于并行計算

@Slf4j
public class ForkJoinTaskExample extends RecursiveTask<Integer> {

    public static final int threshold = 2;
    private int start;
    private int end;

    public ForkJoinTaskExample(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        int sum = 0;

        //如果任務(wù)足夠小就計算任務(wù)
        boolean canCompute = (end - start) <= threshold;
        if (canCompute) {
            for (int i = start; i <= end; i++) {
                sum += i;
            }
        } else {
            // 如果任務(wù)大于閾值,就分裂成兩個子任務(wù)計算
            int middle = (start + end) / 2;
            ForkJoinTaskExample leftTask = new ForkJoinTaskExample(start, middle);
            ForkJoinTaskExample rightTask = new ForkJoinTaskExample(middle + 1, end);

            // 執(zhí)行子任務(wù)
            leftTask.fork();
            rightTask.fork();

            // 等待任務(wù)執(zhí)行結(jié)束合并其結(jié)果
            int leftResult = leftTask.join();
            int rightResult = rightTask.join();

            // 合并子任務(wù)
            sum = leftResult + rightResult;
        }
        return sum;
    }

    public static void main(String[] args) {
        ForkJoinPool forkjoinPool = new ForkJoinPool();

        //生成一個計算任務(wù)靴拱,計算1+2+3+4
        ForkJoinTaskExample task = new ForkJoinTaskExample(1, 100);

        //執(zhí)行一個任務(wù)
        Future<Integer> result = forkjoinPool.submit(task);

        try {
            log.info("result:{}", result.get());
        } catch (Exception e) {
            log.error("exception", e);
        }
    }
}

8-4 J.U.C-BlockingQueue

image.png

image.png

image.png

image.png

第9章 線程調(diào)度-線程池

本章講解J.U.C里最后一部分:線程池垃喊。面試大概率會問到線程池相關(guān)的知識點(diǎn)。這一章將主要從new Thread弊端袜炕、線程池的好處本谜、ThreadPoolExecutor詳細(xì)介紹(參數(shù)、狀態(tài)偎窘、方法)乌助、線程池類圖溜在、Executor框架接口等進(jìn)行講解,需要大家能了解線程池的許多細(xì)節(jié)及配置他托,并能在實(shí)際項(xiàng)目中正確使用炕泳。...

9-1 線程池-1

  • Thread的弊端


    image.png
  • 線程池的好處


    image.png
  • ThreadPoolExecutor


    image.png

    image.png

9-2 線程池-2

  • 執(zhí)行狀態(tài)


    image.png
  • 方法


    image.png

    image.png
  • 線程池類圖


    image.png
  • Executor框架接口


    image.png
  • newCachedThreadPool

@Slf4j
public class ThreadPoolExample1 {

    public static void main(String[] args) {
        ExecutorService executor = Executors.newSingleThreadExecutor();

        for (int i = 0; i < 10; i++) {
            final int index = i;
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    log.info("task:{}", index);
                }
            });
        }
        executor.shutdown();
    }
}
  • newFixedThreadPool
@Slf4j
public class ThreadPoolExample2 {

    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 10; i++) {
            final int index = i;
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    log.info("task:{}", index);
                }
            });
        }
        executor.shutdown();
    }
}
  • newScheduledThreadPool
@Slf4j
public class ThreadPoolExample3 {

    public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);

//        executor.schedule(new Runnable() {
//            @Override
//            public void run() {
//                log.warn("schedule run");
//            }
//        }, 3, TimeUnit.SECONDS);


        // 啟動是延遲1秒然后每隔3秒執(zhí)行任務(wù)
        executor.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                log.warn("schedule run");
            }
        }, 1, 3, TimeUnit.SECONDS);
        //executor.shutdown();
    }
}

9-3 線程池-3

  • 線程池 - 合理配置


    image.png

第10章 多線程并發(fā)拓展

本章會對并發(fā)編程做些補(bǔ)充,但都貼近當(dāng)前的面試上祈,主要講解死鎖產(chǎn)生的條件及預(yù)防、多線程并發(fā)編程的最佳實(shí)踐浙芙、Spring與線程安全登刺、以及面試都特別喜歡問的HashMap和ConcurrentMap源碼細(xì)節(jié)。當(dāng)然嗡呼,面試喜歡問的問題纸俭,對實(shí)際項(xiàng)目開發(fā)也是特別重要的。

10-1 死鎖

  • 死鎖的必要條件


    image.png
@Slf4j
public class DeadLock implements Runnable{

    public int flag = 1;
    private String str1;
    private String str2;

    public DeadLock(String str1, String str2) {
       this.str1 = str1;
       this.str2 = str2;
    }

    @Override
    public void run() {
        if (flag == 1) {
            synchronized (str1) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (str2) {
                    log.info("1");
                }
            }
        }

        if (flag == 0) {
            synchronized (str2) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (str1) {
                    log.info("0");
                }
            }
        }
    }

    public static void main(String[] args) {
        String str1 = new String("a");
        String str2 = new String("b");

        DeadLock td1 = new DeadLock(str1, str2);
        DeadLock td2 = new DeadLock(str1, str2);

        td1.flag = 1;
        td2.flag = 0;
        new Thread(td1).start();
        new Thread(td2).start();
    }
}

10-2 并發(fā)最佳實(shí)踐

image.png

image.png

image.png

10-3 Spring與線程安全

image.png

10-4 HashMap與ConcurrentHashMap解析

  • HashMap


    image.png

    在多線程的環(huán)境下南窗,可能出現(xiàn)循環(huán)鏈表

  • ConcurrentHashMap
    java7使用的分布鎖


    image.png

java8


image.png

10-5 多線程并發(fā)與線程安全總結(jié)

2.jpg

第11章 高并發(fā)之?dāng)U容思路

本章重點(diǎn)是讓大家學(xué)會解決高并發(fā)問題的思路和手段揍很,及重點(diǎn)類的使用。在擴(kuò)容講解時万伤,首先通過例子介紹垂直擴(kuò)容和水平擴(kuò)容的區(qū)別窒悔,之后詳細(xì)介紹數(shù)據(jù)庫的讀操作擴(kuò)展和寫操作擴(kuò)展。擴(kuò)容這個最基本的手段敌买,相信大家都不會有什么問題,關(guān)鍵是根據(jù)實(shí)際場景分析做什么樣的擴(kuò)容。

11-1 高并發(fā)之?dāng)U容思路

  • 擴(kuò)容


    image.png
  • 擴(kuò)容 - 數(shù)據(jù)庫


    image.png

第12章 高并發(fā)之緩存思路

本章講解高并發(fā)中緩存方案挺尿。包含對緩存特征(命中率而姐、最大元素、清空策略)芙粱、影響緩存命中率因素祭玉、緩存分類和應(yīng)用場景(本地緩存、分布式緩存)春畔、高并發(fā)場景下緩存常見問題(緩存一致性脱货、緩存并發(fā)、緩存穿透拐迁、雪崩)等的具體介紹蹭劈。此外,針對大家常用的緩存組件Guava Cache线召、Memcache铺韧、Redis也做了原理性的分析,并且演示缓淹。

12-1 高并發(fā)之緩存-特征哈打、場景及組件介紹-1

19.jpg
  • 緩存特征


    image.png
  • 緩存命中率影響因數(shù)


    image.png
  • 緩存分類和應(yīng)用場景


    image.png

12-2 高并發(fā)之緩存-特征塔逃、場景及組件介紹-2

  • Guava Cache


    20.png
  • Memcache


    21.png

    22.png
  • Redis


    23.png

12-3 高并發(fā)之緩存-redis的使用

12-4 高并發(fā)之緩存-高并發(fā)場景問題及實(shí)戰(zhàn)講解

image.png
  • 緩存一致性


    24.png
  • 緩存并發(fā)問題


    25.png
  • 緩存穿透問題


    26.png
  • 緩存的雪崩現(xiàn)象


    27.png

第13章 高并發(fā)之消息隊(duì)列思路

本章重點(diǎn)介紹了消息隊(duì)列的特性(業(yè)務(wù)無關(guān)、FIFO料仗、容災(zāi)湾盗、性能)、為什么需要消息隊(duì)列以及消息隊(duì)列的好處(業(yè)務(wù)解耦立轧、最終一致性格粪、廣播、錯峰與流控)氛改,并在最后對當(dāng)前比較流行的消息隊(duì)列組件kafka和rabbitmq做了架構(gòu)分析和特性介紹帐萎,讓大家對消息隊(duì)列能有明確的認(rèn)識。

13-1 高并發(fā)之消息隊(duì)列-1

image.png

13-2 高并發(fā)之消息隊(duì)列-2

  • 消息隊(duì)列特性


    image.png
  • 為什么需要消息隊(duì)列


    image.png
  • 消息隊(duì)列好處


    image.png

13-3 高并發(fā)之消息隊(duì)列-3

  • Kafka


    28.png
  • RabbitMQ


    image.png

第14章 高并發(fā)之應(yīng)用拆分思路

本章直接從實(shí)際項(xiàng)目拆分步驟講起胜卤,讓大家可以實(shí)際感受到應(yīng)用拆分的好處和解決的問題疆导,之后引出對應(yīng)用拆分原則(業(yè)務(wù)優(yōu)先、循序漸進(jìn)葛躏、兼顧技術(shù)澈段、可靠測試)和應(yīng)用拆分時思考的內(nèi)容(應(yīng)用之間通信、應(yīng)用之間數(shù)據(jù)庫設(shè)計舰攒、避免事務(wù)跨應(yīng)用)败富,并引出對服務(wù)化Dubbo和微服務(wù)Spring Cloud的框架介紹。

14-1 高并發(fā)之應(yīng)用拆分-1

image.png

14-2 高并發(fā)之應(yīng)用拆分-2

  • 拆分原則


    image.png

    image.png
  • Dubbo


    29.png
  • 微服務(wù)


    30.png

第15章 高并發(fā)之應(yīng)用限流思路

本章從實(shí)際項(xiàng)目保存百萬數(shù)據(jù)的限流場景開始講起摩窃,讓大家感受一下某些高并發(fā)場景下使用限流和不使用限流的區(qū)別囤耳,明確限流的重要作用。之后詳細(xì)介紹了限流常用的四種算法:計數(shù)法偶芍、滑動窗口充择、漏桶算法和令牌桶算法,并對他們做了簡單的對比匪蟀。

15-1 高并發(fā)之應(yīng)用限流-1

31.png
  • 計數(shù)器法


    32.png
  • 滑動窗口


  • 漏桶算法


    34.png
  • 令牌桶算法


    35.png

15-2 高并發(fā)之應(yīng)用限流-2

第16章 高并發(fā)之服務(wù)降級與服務(wù)熔斷思路

本章首先通過舉例讓大家明白什么是服務(wù)降級和服務(wù)熔斷椎麦,之后介紹了服務(wù)降級的分類:自動降級(超時、失敗次數(shù)材彪、故障观挎、限流)和人工降級(開關(guān)),總結(jié)了服務(wù)降級和服務(wù)熔斷的共性(目的段化、最終表現(xiàn)嘁捷、粒度、自治)和區(qū)別(出發(fā)原因显熏、管理目標(biāo)層次雄嚣、實(shí)現(xiàn)方式)以及服務(wù)降級要考慮的問題。最后介紹了Hystrix在服務(wù)降級和服務(wù)熔。

16-1 高并發(fā)之服務(wù)降級與服務(wù)熔斷思路

第17章 高并發(fā)之?dāng)?shù)據(jù)庫切庫分庫分表思路

本章從數(shù)據(jù)庫瓶頸開始講起缓升,引出對數(shù)據(jù)庫切庫分庫分表的介紹鼓鲁。數(shù)據(jù)庫切庫里重點(diǎn)介紹了讀寫分離的設(shè)計,對比支持多數(shù)據(jù)源和分庫的區(qū)別港谊;最后介紹了什么時候該考慮分表骇吭、橫向分表與縱向分表,以及通過mybatis的分頁插件shardbatis2.0實(shí)現(xiàn)數(shù)據(jù)庫分表歧寺。

17-1 高并發(fā)之?dāng)?shù)據(jù)庫切庫分庫分表

  • 數(shù)據(jù)庫的瓶頸


    image.png
  • 數(shù)據(jù)庫切庫


    image.png
  • 數(shù)據(jù)庫分表


    image.png

第18章 高并發(fā)之高可用手段介紹

本章主要介紹了高可用的三個常用手段:任務(wù)調(diào)度系統(tǒng)分布式燥狰、主備切換設(shè)計和引入監(jiān)控報警機(jī)制。任務(wù)調(diào)度系統(tǒng)分布式部分對 elastic-job 的優(yōu)點(diǎn)斜筐、思路碾局、特性等做了介紹,主備切換設(shè)計部分則是對zookeeper的分布式鎖這個典型應(yīng)用進(jìn)行介紹奴艾。

18-1 高并發(fā)之高可用一些手段

image.png

第19章 課程總結(jié)

本章首先對本課程的知識進(jìn)行總結(jié)回顧,然后針對面試中的并發(fā)問題與高并發(fā)問題進(jìn)行提問内斯,希望大家都能有所收獲蕴潦,并期待與大家共同探討并發(fā)與高并發(fā)的話題。

19-1 課程總結(jié)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末俘闯,一起剝皮案震驚了整個濱河市潭苞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌真朗,老刑警劉巖此疹,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異遮婶,居然都是意外死亡蝗碎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門旗扑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蹦骑,“玉大人,你說我怎么就攤上這事臀防∶吖剑” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵袱衷,是天一觀的道長捎废。 經(jīng)常有香客問我,道長致燥,這世上最難降的妖魔是什么登疗? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮嫌蚤,結(jié)果婚禮上谜叹,老公的妹妹穿的比我還像新娘匾寝。我一直安慰自己,他們只是感情好荷腊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布艳悔。 她就那樣靜靜地躺著,像睡著了一般女仰。 火紅的嫁衣襯著肌膚如雪猜年。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天疾忍,我揣著相機(jī)與錄音乔外,去河邊找鬼。 笑死一罩,一個胖子當(dāng)著我的面吹牛杨幼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播聂渊,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼差购,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了汉嗽?” 一聲冷哼從身側(cè)響起欲逃,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎饼暑,沒想到半個月后稳析,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡弓叛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年彰居,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撰筷。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡裕菠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出闭专,到底是詐尸還是另有隱情奴潘,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布影钉,位于F島的核電站画髓,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏平委。R本人自食惡果不足惜奈虾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧肉微,春花似錦匾鸥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至劳曹,卻和暖如春奴愉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背铁孵。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工锭硼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蜕劝。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓檀头,卻偏偏與公主長得像,于是被迫代替她去往敵國和親岖沛。 傳聞我的和親對象是個殘疾皇子暑始,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容