【程序員翻身計劃】Java高性能編程第一章-Java多線程概述-編程思維

目标

  1. 重點:
    • 線程安全的概念
    • 線程通信的方式與應用
    • reactor線程模型
    • 線程數量的優化
    • jdk常用命令
    • Netty框架的作用
  2. 難點
    • java運行的原理
    • 同步關鍵字的原理
    • AQS的抽象
    • JUC的源碼
    • 網絡編程的概念
    • GC機制

class文件内容

文件開頭有一個0xcafebabe特殊的标志。

包含版本、訪問标志、常量池、當前類、超級類、接口、字段、方法、屬性

image-20211108203041410.png 

把class文件的信息存在方法區裡面,有了類 根據類創建對象,存儲在堆内存中,垃圾回收就是這裡。這是線程共享的部分,随虛拟機或者GC創建或銷毀。除了這個區域 還有線程獨占空間,随線程生命周期而創建和銷毀。

JVM運行時數據區.png

  • 方法區:用來存儲加載的類信息、常量、靜态變量、編譯後的代碼等數據。虛拟機規範中,這是一個邏輯區劃,不同的虛拟機不同的實現。oracle的HotSpot在java7中,方法區放在永久代,java8放在元數據空間,并且通過GC機制對這個區域進行管理。

  • 堆内存:分為老年代、新生代(Eden、From Survivor、To Survivor) JVM啟動時創建,存放對象的實例。垃圾回收主要管理堆内存。

堆内存.png

  • 虛拟機棧:每個線程都有一個私有的空間。線程棧由多個棧幀(Stack Frame)組成,一個線程會執行一個或多個方法,一個方法對應一個棧幀。

    棧幀包括:局部變量表、操作數棧、動态鍊接、方法返回地址、附加信息。棧内存默認最大1M,超出抛出StackOverflowError

  • 本地方法棧:使用Native本地方法準備的,超出也會報StackOverflowError,不同虛拟機廠商不同的實現。

  • 程序計數器:記錄當前線程執行字節碼的位置,存儲的是字節碼指令地址,如果執行Native方法,計數器值會為空。

    CPU同一時間隻會執行一條線程中的指令。JVM多線程會輪流切換并分配CPU執行時間的方式。為了線程切換後,需要通過程序計數器來恢複正确的執行位置。

接下來是源文件編譯後字節碼相關的東西,暫不在本次筆記中記錄。【記得有本書是字節碼相關的解讀,立個flag,日後學習!】

image-20211109153611935.png

image-20211109153903730.png

線程狀态

6個狀态

  1. new:尚未啟動的線程的狀态
  2. runnable:可運行線程的線程狀态,等待CPU調度
  3. blocked:線程阻塞等待監視器鎖定的狀态,處于synchronized同步代碼塊或方法中被阻塞。
  4. waiting:等待線程的狀态,不帶超時的方式:object.wait Thread.join LockSupport.pard
  5. timed waiting : 具有指定等待時間的等待線程的線程狀态。帶超時的方式:Thread.sleep Object.wait Thread.join LockSupport.parkNanos LockSupport.parkUntil
  6. Terminated:終止線程的狀态,執行完畢或出現異常。

image-20211109162009061.png

案例1

//新建  運行  終止
System.out.println("#####第一種狀态新建  運行  終止");
Thread thread1 = new Thread(new Runnable() {

    @Override
    public void run() {
        System.out.println("thread1當前狀态:"+Thread.currentThread().getState().toString());
        System.err.println("thread1執行了");

    }
});
System.out.println("沒調用start方法,thread1當前狀态:"+thread1.getState().toString());
thread1.start();
Thread.sleep(2000);
System.out.println("等待兩秒,thread1當前狀态:"+thread1.getState().toString());
複制代碼
#####第一種狀态新建  運行  終止
沒調用start方法,thread1當前狀态:NEW
thread1當前狀态:RUNNABLE
thread1執行了
等待兩秒,thread1當前狀态:TERMINATED
複制代碼

案例2

System.out.println("######第二種 新建 運行  等待 運行  終止(sleep)");
Thread thread2 = new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            Thread.sleep(1500L);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("thread2當前狀态:"+Thread.currentThread().getState().toString());
        System.err.println("thread2執行了");
    }
});
Thread.sleep(2000);
System.out.println("沒調用start方法,thread2當前狀态:" + thread2.getState().toString());
thread2.start();
System.out.println("調用start方法,thread2當前狀态:" + thread2.getState().toString());
Thread.sleep(200);
System.out.println("等待200毫秒,thread2當前狀态:" + thread2.getState().toString());
Thread.sleep(3000);
System.out.println("等待3秒,thread2當前狀态:" + thread2.getState().toString());
複制代碼
######第二種 新建 運行  等待 運行  終止(sleep)
沒調用start方法,thread2當前狀态:NEW
調用start方法,thread2當前狀态:RUNNABLE
等待200毫秒,thread2當前狀态:TIMED_WAITING
thread2當前狀态:RUNNABLE
thread2執行了
等待3秒,thread2當前狀态:TERMINATED
複制代碼

案例3

System.out.println("###第三種 新建  運行  阻塞  運行 終止");
    	
Thread thread = new Thread(new Runnable() {

    @Override
    public void run() {
        synchronized (Test.class) {
            System.out.println("當前狀态:"+Thread.currentThread().getState().toString());
            System.out.println("執行了");
        }
    }
});

synchronized (Test.class) {
    System.out.println("沒調用start方法,當前狀态:"+thread.getState().toString());
    thread.start();
    System.out.println("調用start方法,當前狀态:"+thread.getState().toString());
    Thread.sleep(200);
    System.out.println("200毫秒後,當前狀态:"+thread.getState().toString());
}
Thread.sleep(3000);
System.out.println("3秒後,當前狀态:"+thread.getState().toString());
複制代碼
###第三種 新建  運行  阻塞  運行 終止
沒調用start方法,當前狀态:NEW
調用start方法,當前狀态:RUNNABLE
200毫秒後,當前狀态:BLOCKED
當前狀态:RUNNABLE
執行了
3秒後,當前狀态:TERMINATED
複制代碼

線程終止

  • stop()

    線程不安全,會強行終止線程的所有鎖定。

  • interrupt()

    如果目标線程在調用Object class的wait join sleep方法時被阻塞,那麼interrupt會生效,該線程的中斷狀态将被清除,抛出interruptedException異常。

    如果目标線程是被IO或者NIO中的channel阻塞,IO操作會被中斷或者返回特殊異常值。達到終止的目的。

    如果以上條件都不滿足,則會設置此線程的中斷狀态。

  • 通過狀态位來判斷

    public class StopThread extends Thread{
    	public volatile static boolean flag = true;
    	public static void main(String[] args) throws InterruptedException {
    		new Thread(()-> {
    			while(flag) {
    				try {
    					System.out.println("運行中");
    					Thread.sleep(10000);
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    			}
    		}).start();
    		Thread.sleep(3000);
    		flag = false;
    		System.out.println("結束");
    	}
    }
    複制代碼

CPU緩存及内存屏障

CPU有三級緩存,從123到内存再到硬盤。但是存在一個問題,如果多核cpu讀取同樣的數據進行緩存計算,最終寫入主内存的是以哪個為準?

這個時候就出來了一個緩存一緻性協議,單個cpu對緩存中的數據做了改動,需要通知給其他cpu。

CPU還有一個性能優化手段,運行時指令重排,把讀緩存命令優先執行。

兩個問題:

  1. 緩存中的數據與主内存中的數據并不是實時同步的,各個cpu間緩存的數據也不是實時同步的,在同一個時間點,各個cpu看到的同一内存地址的數據的值可能是不一緻的。
  2. 多核多線程中,指令邏輯無法分辨因果關系,可能出現亂序執行。

解決辦法:内存屏障

  1. 寫内存屏障:在指令後插入Store Barrier,能讓寫入緩存中的最新數據更新寫入主内存,讓其他線程可見。
  2. 讀内存屏障:在指令前插入Load Barrier,可以讓高速緩存中的數據失效,強制重新從主内存中加載數據。

線程通信

要想實現多個線程之間的協同,如 線程執行先後順序,獲取某個線程執行的結果等,設計線程之間相互通信。

  1. 文件共享

  2. 網絡共享

  3. 共享變量

  4. jdk提供的線程協調API

    suspend/resume、wait/notify、park/unpark

JDK中對于需要多線程協作的,提供了對應API支持,典型場景是:生産者-消費者模型(線程阻塞、線程喚醒)

suspend/resume

  1. 同步代碼中使用,suspend挂起之後并不會釋放鎖,容易出現死鎖。

  2. suspend比resume後執行

    被棄用。

wait/notify notifyAll

隻能由同一對象鎖的持有者線程調用,也就是寫在同步塊裡,否則會抛出illegalMonitorStateException異常。

wait:加入該對象的等待集合中,并且放棄當前持有的對象鎖。

雖然wait會自動解鎖,但是對順序有要求,如果在notify被調用之後才開始wait方法的調用,線程會永遠處于WAITING狀态。

//正常的wait
public void waitNotify() throws Exception {
    new Thread(()->{
        if(baozidian == null) {
            synchronized (this) {
                System.out.println("進入等待");
            }
        }
        System.out.println("買到包子");
    }).start();
    Thread.sleep(3000);
    baozidian = new Object();
    synchronized (this) {
        this.notify();
        System.out.println("通知");
    }
}

結果:
進入等待
買到包子
通知
複制代碼

park/unpark

線程調用park則等待許可,unpark為指定線程提供許可。

不要求方法的調用順序。但不會釋放鎖,所以在同步代碼塊中使用可能會死鎖。

/** 死鎖的park/unpark */
public void parkUnparkDeadLockTest() throws Exception {
    // 啟動線程
    Thread consumerThread = new Thread(() -> {
        if (baozidian == null) { // 如果沒包子,則進入等待
            System.out.println("1、進入等待");
            // 當前線程拿到鎖,然後挂起
            synchronized (this) {
                LockSupport.park();
            }
        }
        System.out.println("2、買到包子,回家");
    });
    consumerThread.start();
    // 3秒之後,生産一個包子
    Thread.sleep(3000L);
    baozidian = new Object();
    // 争取到鎖以後,再恢複consumerThread
    synchronized (this) {
        LockSupport.unpark(consumerThread);
    }
    System.out.println("3、通知消費者");
}

結果:
1、進入等待
複制代碼

注意:最好不要使用if語句來判斷是否進入等待狀态。

官方建議應該在循環體中檢查等待狀态,原因是處于等待狀态的線程可能會收到錯誤警報和僞喚醒。

僞喚醒是指線程因為更底層的原因導緻的。

線程封閉

并不是所有時候 都要用到共享數據,shuju被封閉在各自的線程中,就不需要同步。

具體體現有:ThreadLocal、局部變量

ThreadLocal

是一個線程級别的變量,每個線程都有一個ThreadLocal,就是每個線程都擁有了自己獨立的一個變量,競争條件被徹底消除了,在并發模式下,是絕對安全的變量。

線程池

  1. 線程在java中是一個對象,更是操作系統的資源,創建銷毀都需要時間。

  2. java對象占用堆内存,操作系統線程占用系統内存,根據jvm規範,一個線程默認最大棧大小是1M,線程過多會消耗很多内存。

  3. 操作系統需要頻繁切換線程上下文。

    ----->線程池就是為了解決這些問題。

線程池概念

  1. 線程池管理器:創建并管理,創建、銷毀線程池、添加新任務
  2. 工作線程:在沒有任務時處于等待狀态,可以循環執行任務
  3. 任務接口:每個任務必須實現的接口,以供工作線程調度任務的執行,它主要規定了任務的入口,任務執行完後的收尾工作,任務的執行狀态等。
  4. 任務隊列:存放沒有處理的任務。提供一種緩沖機制。

image-20211126151836333.png

線程池API-接口定義和實現類

類型名稱描述
接口 Executor 最上層的接口,定義了**執行任務的方法execute**
接口 ExecutorService 繼承了Executor接口,拓展了Callable、Future、關閉方法
接口 ScheduledExecutorService 繼承了ExecutorService接口,增加了定時任務相關的方法
實現類 ThreadPoolExecutor 基礎、标準的線程池實現
實現類 ScheduledThreadPoolExecutor 繼承了ThreadPoolExecutor,實現了
ScheduledExecutorService中相關定時任務的方法

代碼示例

公共代碼塊:

/**
     * 測試:提交15個執行時間需要三秒,看線程池的情況
     * @param threadPoolExecutor
     */
public void testCommon(ThreadPoolExecutor threadPoolExecutor) throws Exception{
    for (int i=0;i<15;i++){
        int n = i;
        threadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("開始執行:"+n);
                    Thread.sleep(3000l);
                    System.err.println("執行結束:"+n);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        System.out.println("提交任務成功:"+i);
    }

    Thread.sleep(500l);
    System.out.println("當前線程池的數量:"+threadPoolExecutor.getPoolSize());
    System.out.println("當前等待隊列的數量:"+threadPoolExecutor.getQueue().size());
    Thread.sleep(15000l);
    System.out.println("當前線程池的數量:"+threadPoolExecutor.getPoolSize());
    System.out.println("當前等待隊列的數量:"+threadPoolExecutor.getQueue().size());
}
複制代碼

測試方法1:

/**
 * 1、線程池信息: 核心線程數量5,最大數量10,無界隊列,超出核心線程數量的線程存活時間:5秒, 指定拒絕策略的
 *
 * @throws Exception
 */
public void threadPoolExecutorTest1() throws Exception{
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,10,5, TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>());
    testCommon(threadPoolExecutor);
}

//預計的結果:線程池數量5,其他進入等待隊列
複制代碼
測試方法1輸出結果:
提交任務成功:0
開始執行:0
提交任務成功:1
開始執行:1
提交任務成功:2
開始執行:2
提交任務成功:3
提交任務成功:4
提交任務成功:5
提交任務成功:6
提交任務成功:7
提交任務成功:8
提交任務成功:9
提交任務成功:10
提交任務成功:11
提交任務成功:12
提交任務成功:13
提交任務成功:14
開始執行:3
開始執行:4
當前線程池的數量:5
當前等待隊列的數量:10
執行結束:2
執行結束:0
執行結束:4
執行結束:1
執行結束:3
開始執行:5
開始執行:6
開始執行:7
開始執行:8
開始執行:9
開始執行:10
開始執行:11
開始執行:12
開始執行:13
開始執行:14
執行結束:5
執行結束:6
執行結束:8
執行結束:7
執行結束:9
執行結束:13
執行結束:10
執行結束:14
執行結束:12
執行結束:11
當前線程池的數量:5
當前等待隊列的數量:0
複制代碼

這裡有一個問題就是,最大線程數量設置的是10,當前線程池的數量為什麼達不到最大線程數量?

這就需要對execute的過程有個了解。

image-20211127101606123.png

測試方法2:

/**
     * 2、 線程池信息: 核心線程數量5,最大數量10,隊列大小3,超出核心線程數量的線程存活時間:5秒, 指定拒絕策略的
     *
     * @throws Exception
     */
    public void threadPoolExecutorTest2() throws Exception{
        // 創建一個 核心線程數量為5,最大數量為10,等待隊列最大是3 的線程池,也就是最大容納13個任務。
        // 如果不指定拒絕策略,默認的策略是抛出RejectedExecutionException異常,java.util.concurrent.ThreadPoolExecutor.AbortPolicy
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(3), new RejectedExecutionHandler() {
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                System.out.println("任務決絕執行。");
            }
        });

        testCommon(threadPoolExecutor);
    }

//執行預期結果:
//線程池數量5,3個進入等待,這時候核心線程數量和隊列都滿了,會加開5個任務線程(注意,5秒後沒任務執行會銷毀),因為最大線程是10
//最大10+等待隊列3   總共13,剩下兩個拒絕執行
複制代碼

測試方法3:Executors.newFixedThreadPool(int nThreads)

對于無界隊列,最大線程數量實際上是不起作用的。

    /**
     * 3、 線程池信息: 核心線程數量5,最大數量5,無界隊列,超出核心線程數量的線程存活時間:5秒
     *
     * @throws Exception
     */
    private void threadPoolExecutorTest3() throws Exception {
        // 和Executors.newFixedThreadPool(int nThreads)一樣的
        ThreadPoolExecutor threadPoolExecutor1 = (ThreadPoolExecutor) Executors.newFixedThreadPool(5);
//        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,
//                new LinkedBlockingQueue<Runnable>());
        testCommon(threadPoolExecutor1);
        // 預計結:線程池線程數量為:5,超出數量的任務,其他的進入隊列中等待被執行
    }
複制代碼

Executors.newFixedThreadPool()的内部實現實際上就是 注釋掉的部分:

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
複制代碼

測試方法4:Executors.newCachedThreadPool()

此種方法適用于不可預估數量的情況

/**
     * 4、 線程池信息:
     * 核心線程數量0,最大數量Integer.MAX_VALUE,SynchronousQueue隊列,超出核心線程數量的線程存活時間:60秒
     *
     * @throws Exception
     */
    private void threadPoolExecutorTest4() throws Exception {

        // SynchronousQueue,實際上它不是一個真正的隊列,因為它不會為隊列中元素維護存儲空間。與其他隊列不同的是,它維護一組線程,這些線程在等待着把元素加入或移出隊列。
        // 在使用SynchronousQueue作為工作隊列的前提下,客戶端代碼向線程池提交任務時,
        // 而線程池中又沒有空閑的線程能夠從SynchronousQueue隊列實例中取一個任務,
        // 那麼相應的offer方法調用就會失敗(即任務沒有被存入工作隊列)。
        // 此時,ThreadPoolExecutor會新建一個新的工作者線程用于對這個入隊列失敗的任務進行處理(假設此時線程池的大小還未達到其最大線程池大小maximumPoolSize)。

        // 和Executors.newCachedThreadPool()一樣的
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>());
        testCommon(threadPoolExecutor);
        // 預計結果:
        // 1、 線程池線程數量為:15,超出數量的任務,其他的進入隊列中等待被執行
        // 2、 所有任務執行結束,60秒後,如果無任務可執行,所有線程全部被銷毀,池的大小恢複為0
        Thread.sleep(60000L);
        System.out.println("60秒後,再看線程池中的數量:" + threadPoolExecutor.getPoolSize());
    }
複制代碼
 測試方法4輸出結果:
提交任務成功:0
提交任務成功:1
提交任務成功:2
提交任務成功:3
提交任務成功:4
提交任務成功:5
提交任務成功:6
提交任務成功:7
提交任務成功:8
提交任務成功:9
提交任務成功:10
提交任務成功:11
提交任務成功:12
提交任務成功:13
提交任務成功:14
開始執行:3
開始執行:2
開始執行:6
開始執行:7
開始執行:10
開始執行:11
開始執行:0
開始執行:1
開始執行:5
開始執行:4
開始執行:8
開始執行:9
開始執行:12
開始執行:13
開始執行:14
當前線程池的數量:15
當前等待隊列的數量:0
執行結束:3
執行結束:2
執行結束:6
執行結束:7
執行結束:10
執行結束:9
執行結束:0
執行結束:1
執行結束:5
執行結束:4
執行結束:14
執行結束:8
執行結束:11
執行結束:12
執行結束:13
當前線程池的數量:15
當前等待隊列的數量:0
60秒後,再看線程池中的數量:0

複制代碼

測試方法5:一次性定時任務

/**
     * 5、 定時執行線程池信息:3秒後執行,一次性任務,到點就執行 <br/>
     * 核心線程數量5,最大數量Integer.MAX_VALUE,DelayedWorkQueue延時隊列,超出核心線程數量的線程存活時間:0秒
     *
     * @throws Exception
     */
public void threadPoolExecutorTest5() throws Exception{
    //Executors.newScheduledThreadPool() 一樣的
    ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(5);
    scheduledThreadPoolExecutor.schedule(new Runnable() {
        @Override
        public void run() {
            System.out.println("任務被執行,現在時間:"+ DateUtil.now());
        }
    },3000,TimeUnit.MILLISECONDS);
    System.out.println("定時任務,提交成功,時間是:"+DateUtil.now());
}
複制代碼

測試方法5輸出結果:

定時任務,提交成功,時間是:2021-11-27 13:42:43
任務被執行,現在時間:2021-11-27 13:42:46
複制代碼

測試方法6:周期定時任務

/**
     * 6、 定時執行線程池信息:線程固定數量5 ,<br/>
     * 核心線程數量5,最大數量Integer.MAX_VALUE,DelayedWorkQueue延時隊列,超出核心線程數量的線程存活時間:0秒
     *
     * @throws Exception
     */
public void threadPoolExecutorTest6() throws Exception{
    ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(5);
    //第一種方式:scheduleAtFixedRate,如果執行時間超過了周期時間
    //執行完畢後,立即執行,不考慮延遲時間
    scheduledThreadPoolExecutor.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(3000l);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("任務1被執行,現在時間:"+DateUtil.now());
        }
    },2000,1000,TimeUnit.MILLISECONDS);
    //第二種方式,scheduleWithFixedDelay
    //如果執行時間超過了周期時間,執行完畢後,加上延遲時間後再執行
    scheduledThreadPoolExecutor.scheduleWithFixedDelay(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(3000l);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("任務2被執行,現在時間:"+DateUtil.now());
        }
    },2000,1000,TimeUnit.MILLISECONDS);
}
複制代碼

測試方法6輸出結果:

//可以看出,任務1每隔3秒執行一次,任務2每隔4秒執行一次
任務1被執行,現在時間:2021-11-27 14:08:45
任務2被執行,現在時間:2021-11-27 14:08:45
任務1被執行,現在時間:2021-11-27 14:08:48
任務2被執行,現在時間:2021-11-27 14:08:49
任務1被執行,現在時間:2021-11-27 14:08:51
任務2被執行,現在時間:2021-11-27 14:08:53
任務1被執行,現在時間:2021-11-27 14:08:54
任務1被執行,現在時間:2021-11-27 14:08:57
任務2被執行,現在時間:2021-11-27 14:08:57
複制代碼

終止線程的兩種方式

scheduledThreadPoolExecutor.shutdown();
//第二種會返回尚未執行的任務
List<Runnable> runnableList = scheduledThreadPoolExecutor.shutdownNow();
複制代碼

本文同步公衆号【劉墨澤】,歡迎大家關注聊天!

關于多線程的三兩事-編程思維

多線程一直是編程中的重要的工具,它可以分充分的利用硬件資源,是我們用更少的時間去完成更多的事情。在之前的博客中,我有介紹了OpenMP的基本使用,OpenMP可以理解為多線程的一個合理和高效的一套抽象工具。這次,打算仔細的介紹多線程編程中的常見的概念和典型的案例。 典型的案例 說到多線程,最核心的問題就是保證數據的讀寫

異步、多線程、Await/Async、Task-編程思維

異步多線程經常被拿來說事,網上文章也是多如牛毛,我也是停留在很菜的水平,痛下決心好好“惡補”一下這塊知識。 還是先放兩個官方文檔壓壓驚:使用 Async 和 Await 的異步編程    .NET 中的并行處理、并發和異步編程 學習前: 耗時操作知道搞個Task.Run,不用等待結果就後台執行,Api直接返回,多個任務

線程池子線程超時(僵死)問題-編程思維

簡介 線程池循環執行一些任務,某個線程執行超時,需要将超時的線程任務抛棄。 示例 修改前 當遇到超時的任務就涼涼,得重啟程序。 Task.java: public class Task implements Runnable { private final int sleepTime; private fin

7個連環問揭開java多線程背後的彎彎繞-編程思維

摘要:很多java入門新人一想到java多線程, 就會覺得很暈很繞,什麼可見不可見的,也不了解為什麼sync怎麼就鎖住了代碼。 本文分享自華為雲社區《java多線程背後的彎彎繞繞到底是什麼? 7個連環問題為你逐步揭開背後的核心原理!》,作者:breakDraw 。 很多java入門新人一想到java多線程, 就會覺得很

Java 多線程的一次整理-編程思維

一天沒有出過家門,實屬無聊,沒事瞎寫寫 1. 基本概念 1.1 多進程和多線程的概念 程序是由指令和數據組成,指令要運行,數據要加載,指令被 CPU 加載運行,數據被加載到内存,指令運行時可由 CPU 調度硬盤、網絡等設備。一個線程就是一個指令,CPU 調度的最小單位,一個進程就是一系列的指令流,由 CPU 一條一條執

HeapDump性能社區Full GC異常問題排查實戰案例精選合集-編程思維

處理過線上問題的同學基本都遇到過系統突然運行緩慢,CPU 100%,以及 Full GC 次數過多的問題。這些問題最終導緻的直觀現象就是系統運行緩慢,并且有大量的報警。 本期小編集合了HeapDump性能社區内的4篇Full GC異常問題排查文章,通過幾位作者記錄的真實案例,提醒自己避免踩坑,順便複習相關知識點。 1.

Java程序員漲薪必備的性能調優知識點,收好了!-編程思維

Java 應用性能優化是一個老生常談的話題,典型的性能問題如頁面響應慢、接口超時,服務器負載高、并發數低,數據庫頻繁死鎖等。尤其是在“糙快猛”的互聯網開發模式大行其道的今天,随着系統訪問量的增加和代碼的日漸臃腫,各種性能問題開始紛至沓來。Java 應用性能的瓶頸點非常多,比如磁盤、内存、網絡 I/O 等系統因素,Jav

壞代碼導緻的性能問題大賞:CPU占用飙到了900%!-編程思維

讀過《重構 - 改善既有代碼的設計》一書的同學們應該都很了解“代碼的壞味道”。當然确定什麼是代碼“壞味道”是主觀的,它會随語言、開發人員和開發方法的不同而不同。在工作當中,很多時候都是在維護之前的項目和在此基礎上增加一些新功能,為了能讓項目代碼易于理解和維護,要時刻注意代碼中的“壞味道”,當發現代碼如果有壞味道了,要及

Redis 很屌,不懂使用規範就糟蹋了-編程思維

這可能是最中肯的 Redis 使用規範了 碼哥,昨天我被公司 Leader 批評了。 我在單身紅娘婚戀類型互聯網公司工作,在雙十一推出下單就送女朋友的活動。 誰曾想,淩晨 12 點之後,用戶量暴增,出現了一個技術故障,用戶無法下單,當時老大火冒三丈! 經過查找發現 Redis 報 Could not get a re

SpringBoot Profiles 多環境配置及切換-編程思維

目錄前言默認環境配置多環境配置多環境切換小結 前言 大部分情況下,我們開發的産品應用都會根據不同的目的,支持運行在不同的環境(Profile)下,比如: 開發環境(dev) 測試環境(test) 預覽環境(pre) 生産環境(prod) 這裡的 環境 實際上是一個統稱,不同的環境可能代表着 使用的域名、端口、實例數目是