主頁 > 後端開發 > 多執行緒JUC并發篇常見面試詳解

多執行緒JUC并發篇常見面試詳解

2022-04-17 06:23:47 後端開發

@

目錄
  • 1、JUC 簡介
    • 2、執行緒和行程
  • 3、并非與并行
  • 4、執行緒的狀態
  • 5、wait/sleep的區別
  • 6、Lock 鎖(重點)
    • 1、Lock鎖
    • 2、公平非公平:
    • 3、ReentrantLock 構造器
    • 4、Lock 鎖實作步驟:
  • 7、synchronized 和 lock 鎖的區別
  • 8、生產者和消費者問題(通信問題)
    • 1、Synchronized 版本
    • 2、JUC 版本
  • 9、八個有關鎖的問題
      • 關于鎖的八個問題
    • 問題1:兩個同步方法,先執行發短信還是打電話?
    • 問題2:如果發短信延遲2秒,誰先執行
    • 問題3 加上一個沒有鎖的普通方法,誰先執行
    • 問題4:兩個物件,一個呼叫發短信,一個呼叫打電話,誰先執行
    • 問題5:原來的兩個同步方法,變為靜態同步方法,一個物件呼叫,誰先執行
    • 問題6:創建兩個實體,呼叫兩個靜態同步方法,誰先執行
    • 問題7:一個靜態同步方法、一個同步方法、一個物件呼叫,誰先執行
    • 問題8:兩個物件,一個呼叫靜態同步方法,一個呼叫普通同步方法,誰先執行
    • 小結
  • 10、集合類的安全問題
    • 1、List 不安全
    • 2、Set 不安全
  • 11、Callable(簡單)
  • 12、JUC 常用輔助類
    • 1、CountDownLatch
    • 2、CyclickBarrier
    • 3、Semaphore
  • 13、ReadWriteLock 讀寫鎖
  • 14、阻塞佇列
    • 1、Blockqueue
    • 2、SynchronizedQueue
  • 15、執行緒池(重點)
    • 1、執行緒池:三大方法
    • 2、執行緒池:七大引數
      • 3、四大拒絕策瑜:
  • 16、為什么要使用執行緒池?
  • 17、執行緒池執行緒復用的原理是什么?
  • 18、AQS的理解
        • 1、ReentrantLock和AQS的關系
        • 2、ReentrantLock加鎖和釋放鎖的底層原理
  • 19、執行緒創建的三種方式
  • 20、為什么啟動start(),就呼叫run方法
  • 21、執行緒的生命周期
  • 21、執行緒安全:
        • 執行緒安全解決問題方案:
    • 1、互斥阻塞同步:也就是加鎖sychronized和ReenrtrantLock,加鎖優缺點?
  • 22、執行緒同步機制
  • 23、run()方法和sart()方法有什么區別
  • 24、執行緒是否可以被重復啟動
  • 25、volatile
  • 26、java多執行緒之間的三種通信方式
    • 1、synchronized來保證執行緒安全
    • 2、通過Lock()
    • 3、BlockingQueue
  • 27、說一說synchronized的底層實作原理
  • 28、CAS
    • 1、概念
    • 2、CAS可能產生ABA問題:
  • 29、鎖升級初步
    • 1、偏向鎖:
    • 2、輕量級鎖
    • 3、鎖重入鎖
    • 4、自旋鎖什么時候升級為重量級鎖
    • 5、為什么有自旋鎖還需要重量級鎖
    • 6、偏向鎖是否一定比自旋鎖效率高
  • 30、ThreadLocal機制
  • 31、ThreadLocal機制的記憶體泄露
          • 留言:

多執行緒JUC并發篇

1、JUC 簡介

什么是 JUC ?

  • JUC 就是 java.util.concurrent 下面的類包,專門用于多執行緒的開發
    在這里插入圖片描述
    為什么使用 JUC ?

  • 以往我們所學,普通的執行緒代碼,都是用的thread或者runnable介面

  • 但是相比于callable來說,thread沒有回傳值,且效率沒有callable高

2、執行緒和行程

  • 行程就是一個應用程式

  • 執行緒是行程中的一個物體,執行緒本身是不會獨立存在的,

行程是代碼在資料集合上的一次運行活動, 是系統進行資源分配和調度的基本單位,

執行緒則是行程的一個執行路徑, 一個行程中至少有一個執行緒,行程中的多個執行緒共享行程的資源,

? 作業系統在分配資源時是把資源分配給行程的, 但是CPU 資源比較特殊, 它是被分配到執行緒的, 因為真正要占用CPU 運行的是執行緒, 所以也說執行緒是CPU 分配的基本單位,

? java默認有幾個執行緒? 兩個 main執行緒 gc執行緒

? Java 中,使用 Thread、Runnable、Callable 開啟執行緒,

? Java 沒有權限開啟執行緒 、Thread.start() 方法呼叫了一個 native 方法 start0(),它呼叫了底層 C++ 代碼,

3、并非與并行

并發多執行緒操作同一個資源,交替執行

  • CPU一核, 模擬出來多條執行緒,天下武功,唯快不破,快速交替

并行(多個人一起行走, 同時進行)

  • CPU多核,多個執行緒同時進行 ; 使用執行緒池操作

4、執行緒的狀態

  • 新建

  • 就緒

  • 阻塞

  • 運行

  • 死亡

5、wait/sleep的區別

  • 來自不同的類:wait來自object類, sleep來自執行緒類

  • 關于鎖的釋放:wait會釋放鎖, sleep不會釋放鎖

  • 使用范圍不同:wait必須在同步代碼塊中,sleep可以在任何地方睡

  • 是否需要捕獲例外:wait不需要捕獲例外,sleep需要捕獲例外

6、Lock 鎖(重點)

Synchronized 傳統的鎖

之前我們所學的使用執行緒的傳統思路是:

  • 單獨創建一個執行緒類,繼承Thread或者實作Runnable

  • 在這個執行緒類中,重寫run方法,同時添加相應的業務邏輯

  • 在主執行緒所在方法中new上面的執行緒物件,呼叫start方法啟動

1、Lock鎖

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-aFbI65Pz-1650013582522)(https://pizximfzuc.feishu.cn/space/api/box/stream/download/asynccode/?code=Nzg2NDU1MDkyYTk1YWVlZDJjMzM3M2QxODNlMWM4NWRfeUZORjNjcWtmd3ZrR1FmWEZ2MkVWdjZMWGtHenpsM3JfVG9rZW46Ym94Y25qVzdjWERnR2owZVlMRXU4S3pTT1VjXzE2NTAwMTM0OTI6MTY1MDAxNzA5Ml9WNA)]
可以看到,

Lock是一個介面,有三個實作類,現在我們使用

ReentrantLock 就夠用了

查看

ReentrantLock 原始碼,構造器

2、公平非公平:

  • 公平鎖::十分公平, 可以先來后到,一定要排隊

  • 非公平鎖::十分不公平,可以插隊(默認)

3、ReentrantLock 構造器

  • ReentrantLock 默認的構造方法是非公平鎖(可以插隊),

  • 如果在構造方法中傳入 true 則構造公平鎖(不可以插隊,先來后到),

4、Lock 鎖實作步驟:

  1. 創建鎖,new ReentrantLock()
  2. 加鎖,lock.lock()
  3. 解鎖,lock.unlock()
  4. 基本結構固定,中間的業務自己靈活修改

7、synchronized 和 lock 鎖的區別

  1. synchronized 是內置的 Java 關鍵字,Lock 是一個 Java 類

  2. synchronized 無法判斷獲取鎖的狀態,Lock可以判斷是否獲取到了鎖

  3. synchronized 會自動釋放鎖,Lock 必須要手動釋放鎖!如果不釋放鎖,會產生死鎖

  4. synchronized 假設執行緒1(獲得鎖,然后發生阻塞),執行緒2(一直等待); Lock 鎖就不一定會等待下去,可使用 tryLock 嘗試獲取鎖

  5. synchronized 可重入鎖,不可以中斷的,非公平的;Lock鎖,可重入的,可以判斷鎖,是否公平(可自己設定)

  6. synchronized 適合鎖少量的代碼同步問題,Lock 適合鎖大量的同步代碼

總體來說,synchronized 本來就是一個關鍵字,很多規則都是定死的,靈活性差;Lock 是一個類,靈活性高

8、生產者和消費者問題(通信問題)

1、Synchronized 版本

解決執行緒之間的通信問題,比如執行緒操作一個公共的資源類

基本流程可以總結為:

  • 等待:判斷是否需要等待

  • 業務:執行相應的業務

  • 通知:執行完業務通知其他執行緒

public class ConsumeAndProduct {
    public static void main(String[] args) {
        Data data = https://www.cnblogs.com/zbqblogs/archive/2022/04/16/new Data();
        // 創建一個生產者
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        // 創建一個消費者
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
    }
}
//這是一個緩沖類,生產和消費之間的倉庫,公共資源類
class Data{
    // 這是倉庫的資源,生產者生產資源,消費者消費資源
    private int num = 0;
    // +1,利用關鍵字加鎖
    public synchronized void increment() throws InterruptedException {
        // 首先查看倉庫中的資源(num),如果資源不為0,就利用 wait 方法等待消費,釋放鎖
        if(num!=0){
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        // 通知其他執行緒 +1 執行完畢
        this.notifyAll();
    }
    // -1
    public synchronized void decrement() throws InterruptedException {
        // 首先查看倉庫中的資源(num),如果資源為0,就利用 wait 方法等待生產,釋放鎖
        if(num==0){
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        // 通知其他執行緒 -1 執行完畢
        this.notifyAll();
    }
}

思考問題:如果存在ABCD4個執行緒是否安全?

  • 不安全,會有虛假喚醒

查看 api 檔案
在這里插入圖片描述

解決辦法:if 判斷改為 while,防止虛假喚醒

  • 因為 if 只會執行一次,執行完會接著向下執行 if() 外邊的代碼

  • 而 while 不會,直到條件滿足才會向下執行 while() 外邊的代碼

修改代碼為:

    // ...
    // 使用 if 存在虛假喚醒
    while (num!=0){
        this.wait();
    }
    // ...
    while(num==0){
        this.wait();
    }

2、JUC 版本

鎖、等待、喚醒 都進行了更換
在這里插入圖片描述

改造之后,確實可以實作01切換,但是ABCD是無序的,不滿足我們的要求,

Condition 的優勢在于,精準的通知和喚醒執行緒!比如,指定通知下一個進行順序,

重新舉個例子,

三個執行緒 A執行完呼叫B,B執行完呼叫C,C執行完呼叫A,分別用不同的監視器,執行完業務后指定喚醒哪一個監視器,實作執行緒的順序執行

鎖是統一的,但監視器是分別指定的,分別喚醒,signal,之前使用的是 signalAll

  private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int num = 1; // 1A 2B 3C
    public void printA(){
        lock.lock();
        try {
            while (num != 1){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + " Im A ");
            num = 2;
            condition2.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printB(){
        lock.lock();
        try {
            while (num != 2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + " Im B ");
            num = 3;
            condition3.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printC(){
        lock.lock();
        try {
            while (num != 3) {
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + " Im C ");
            num = 1;
            condition1.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

9、八個有關鎖的問題

深入理解鎖

關于鎖的八個問題

問題1:兩個同步方法,先執行發短信還是打電話?

經過測驗,一直是先發短信

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-rsXbJVS7-1650013582524)(https://pizximfzuc.feishu.cn/space/api/box/stream/download/asynccode/?code=NzExNGI3OTliYmQ5MTk5ZmVjMjYzNzAxM2MwYmQwMGJfM3l3b1NKWndKYVA5cjRrVHc4SWdxektzOGQxSmZKeHVfVG9rZW46Ym94Y25nTkhuUVdVdllwVWhiYVZXS09Oak0xXzE2NTAwMTM0OTI6MTY1MDAxNzA5Ml9WNA)]

問題2:如果發短信延遲2秒,誰先執行

結果依舊是先發短信,后打電話

分析:

  • 并不是由于發短信在前導致的

  • 本案例中,方法前加synchronized,鎖的其實該方法的呼叫者,也就是 phone 實體,兩個方法共用同一個 phone 物件的鎖,誰先拿到,誰先執行

  • 在主執行緒中,先呼叫發短信,所以先執行,打電話等釋放鎖再執行

問題3 加上一個沒有鎖的普通方法,誰先執行

觀察發現,先執行了 hello

分析原因:

  • hello 是一個普通方法,不受 synchronized 鎖的影響,不用等待鎖釋放,

問題4:兩個物件,一個呼叫發短信,一個呼叫打電話,誰先執行

結論,先打電話,后發短信

分析原因:

  • 兩個物件兩把鎖,互不影響,1拿到鎖還需要等待3秒,2拿到物件立刻就能打電

問題5:原來的兩個同步方法,變為靜態同步方法,一個物件呼叫,誰先執行

結果,始終是先發短信,后打電話

分析原因:

靜態方法前面加鎖,鎖的其實是這個方法所在的Class類物件(非靜態那個是實體物件,注意區分)

Class類物件也是全域唯一,使用的是通一把鎖,所以先發短信,后打電話

雖然和上面的實體物件都是對應了全域唯一的鎖,但原理還是有所不同

主執行緒先執行了發短信,打電話就必須等鎖釋放再執行

問題6:創建兩個實體,呼叫兩個靜態同步方法,誰先執行

結果,現發短信,后打電話

原因分析:

  • 雖然實體物件是兩個,但是兩個靜態同步方法對應的鎖是Class類物件的鎖,還是全域唯一

問題7:一個靜態同步方法、一個同步方法、一個物件呼叫,誰先執行

結果:先打電話,后發短信

原因分析:

  • 靜態同步方法和普通同步方法分別對應了不同的鎖,互不干擾

  • 發短信需要延遲3秒,所以打電話先執行了

問題8:兩個物件,一個呼叫靜態同步方法,一個呼叫普通同步方法,誰先執行

結果,先打電話,后發短信

分析原因:

同問題7相同,兩個方法對應了不同的鎖,互不干擾

發短信還需要等待3秒,所以打電話先執行完了

小結

無外乎兩種鎖,一個是new實體的鎖,一個是Class物件的鎖

實體的鎖,與當前的實體唯一對應,Class物件的鎖與這個類唯一對應

如果兩個方法等同一個鎖,必須一個先執行完,釋放鎖,另一個才可以執行

如果兩個方法等不同的鎖,互不影響,誰先誰后看具體情況

在主執行緒中,代碼是順序執行的,再結合鎖的原理,綜合判斷執行緒執行的順序

10、集合類的安全問題

在 JUC 并發編程情況下,適用于單執行緒的集合類將出現并發問題

1、List 不安全

運行出現并發修改例外,

java.util.ConcurrentModificationException

解決方案:

解決方案1:

  • ArrayList 換成 Vector,Vector 方法里加了鎖

  • Vector出現比較早,由于鎖導致方法執行效率太低,不推薦使用

解決方案2:

  • 使用 Collection 靜態方法,回傳一個帶鎖的 List 實體

List list = Collections.synchronizedList(new ArrayList<>());

解決方案3:

  • 使用 JUC 提供的適合并發使用的 CopyOnWriteArrayList

List list = new CopyOnWriteArrayList<>();

分析:

CopyOnWrite 表示寫入時復制,簡稱COW,計算機程式設計領域的一種優化策略

多執行緒呼叫list時,讀取時沒有問題,寫入的時候會復制一份,避免在寫入時被覆寫

這也是一種讀寫分離的思想

CopyOnWriteArrayList 比 Vector 強在哪里?前者是寫入、復制,且使用 lock 鎖,效率比 Vector 的synchronized 鎖要高很多

2、Set 不安全

Set 和 List 同理可得:多執行緒情況下,普通的 Set 集合是執行緒不安全的

  • 使用 Collection 工具類的 synchronized 包裝的 Set 類

Set set = Collections.synchronizedSet(new HashSet<>());

  • 使用 JUC 提供的 CopyOnWriteArraySet 寫入復制

Set set = new CopyOnWriteArraySet<>();

思考,HashSet 底層到底是什么?

  • hashSet底層就是一個HashMap;hashSet只使用了hashMap的key

11、Callable(簡單)

得到的資訊

可以有回傳值

可以拋出例外

方法不同,run() => call()

使用時注意

  • Callable 的泛型也是 call 方法的回傳值型別

  • Callable 的實作類無法直接放在 Thread 中,還需要先放在 -

  • FutureTask 中,再放在 Thread 中FutureTask 就相當于適配類,起到牽線的作用

注意:

  • 運行結果會產生快取,目的是為了提高效率

  • get方法可能會產生阻塞,所以放在了最后

12、JUC 常用輔助類

1、CountDownLatch

減法計數器

原理:

countDownLatch.countDown(); //數量減1

countDownLatch.await();// 等待計數器歸零,然后再向下執行

每次有執行緒呼叫countDown()數量-1,假設計數器變為0,countDownLatch.await();就會被喚醒,繼續執行

2、CyclickBarrier

加法計數器,與 CountDownLatch 正好相反

相當于設定一個目標,執行緒數達到目標值之后才會執行

3、Semaphore

計數信號量,比如說,有6輛車,3個停車位,汽車需要輪流等待車位

常用在需要限流的場景中,

原理:

  • *semaphore.acquire() 獲得資源,如果資源已經使用完了,就等待資源釋放后再進行使用!

  • *semaphore.release() 釋放,會將當前的信號量釋放+1,然后喚醒等待的執行緒!

用途:

  • *多個共享資源互斥的使用!

  • *并發限流,控制最大的執行緒數!

13、ReadWriteLock 讀寫鎖

ReadWriteLock,這是一個更加細粒度的鎖

// 自定義快取
class MyCache{
    private volatile Map<String,String> map = new HashMap<>();
    private ReadWriteLock readWriteLock= new ReentrantReadWriteLock();
    // 存,寫,寫入的時候只希望只有一個執行緒在寫
    public void write(String key, String value) {
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "執行緒開始寫入");
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "執行緒開始寫入ok");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }
    // 取,讀,所有執行緒都可以讀
    public void read(String key) {
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "執行緒開始讀取");
            map.get(key);
            System.out.println(Thread.currentThread().getName() + "執行緒讀取ok");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}

小結:

  • 讀-讀 可以共存
  • 讀-寫 不能共存
  • 寫-寫 不能共存

也可以這樣稱呼,含義都是一樣,名字不同而已

  • 獨占鎖(寫鎖)一次只能由一個執行緒占有
  • 享鎖(讀鎖)一次可以有多個執行緒占有

14、阻塞佇列

1、Blockqueue

阻塞佇列 BlockQueue 是Collection 的一個子類

應用場景:多執行緒并發處理、執行緒池

BlockingQueue 有四組 API

方式 拋出例外 不會拋出例外,有回傳值 阻塞等待 超時等待

添加操作 add() offer() 供應 put() offer(obj,int,timeunit.status)可設定時間

移除操作 remove() poll() 獲得 take() poll(int,timeunit.status)可設定時間

判斷佇列首部 element() peek() 偷看,偷窺 SynchronizedQueue 同步佇列

同步佇列沒有容量,進去一個元素,必須等待取出來之后,才能再往里面放一個元素

2、SynchronizedQueue

  • SynchronizedQueue使用 put 方法和 take 方法
  • Synchronized 和 其他的 BlockingQueue 不一樣 它不存盤元素;
  • put了一個元素,就必須從里面先 take 出來,否則不能再 put 進去值!
  • 并且 SynchronousQueue 的 take 是使用了 lock 鎖保證執行緒安全的,

15、執行緒池(重點)

池化技術

執行緒池重點:三大方式、七大引數、四種拒絕策略

程式的運行的本質:占用系統的資源 ! 優化CPU資源的使用 ===>池化技術(執行緒池、連接池、記憶體池、物件池…)

池化技術:實作準備好一些資源,有人要用,就來我這里拿,用完之后還給我

執行緒池的好處:

  • 降低資源消耗
  • 提高回應速度
  • 方便管理

如何優化:

  • 執行緒復用,可以控制最大并發數,管理執行緒

1、執行緒池:三大方法

查看阿里巴巴開發手冊
在這里插入圖片描述

  1. ExecutorService threadPool = Executors.newSingleThreadExecutor();//單個執行緒
  2. ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //創建一個固定的執行緒池的大小
  3. ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸縮的(不會出現OOM)

之前我們所學知識,直接創建執行緒,現在我們通過執行緒池來創建執行緒,使用池化技術

>  ExecutorService service = Executors.newCachedThreadPool();//可伸縮的,遇強則強,遇弱則弱
>         try {
>             for (int i = 0; i < 10; i++) {
>                 service.execute(() -> {
>                     System.out.println(Thread.currentThread().getName() + "ok");
>                 });
>             }
>             //執行緒池用完要關閉執行緒池
>         } finally {
>             service.shutdown();
>         }

2、執行緒池:七大引數

public ThreadPoolExecutor(int corePoolSize,//核心執行緒數 也就是一直作業的執行緒數量
                          int maximumPoolSize,//最大執行緒數,如果核心心執行緒數使用完
                          long keepAliveTime,//非核心執行緒的存活時間
                          TimeUnit unit,//非核心執行緒的存活時間單位
                          BlockingQueue<Runnable> workQueue,//阻塞佇列
                          ThreadFactory threadFactory,//執行緒工廠
                          RejectedExecutionHandler handler) //拒絕策略

提交優先級

execute()提交方法中原始碼中的幾個if里面都會呼叫執行方法addWorker(Rannale firstTask,boolean core )
在這里插入圖片描述

執行優先級
在這里插入圖片描述
在這里插入圖片描述

執行優先級:

addWorker(Rannale firstTask,boolean core )

submit()與execute()區別

1、submit()有回傳值,execute()沒有回傳值

2、submit()方法里面呼叫了execute()方法

3、四大拒絕策瑜:

在這里插入圖片描述

16、為什么要使用執行緒池?

為了減少創建和銷毀執行緒的次數,讓每個執行緒可以多次使用,可根據系統情況調整執行的執行緒數量,防止消耗過多記憶體,所以我們可以使用執行緒池.

17、執行緒池執行緒復用的原理是什么?

首先執行緒池內的執行緒都被包裝成了一個個的java.util.concurrent.ThreadPoolExecutor.Worker,然后這個worker會馬不停蹄的執行任務,執行完任務之后就會在while回圈中去取任務,取到任務就繼續執行,取不到任務就跳出while回圈(這個時候worker就不能再執行任務了)執行 processWorkerExit方法,這個方法呢就是做清場處理,將當前woker執行緒從執行緒池中移除,并且判斷是否是例外的進入processWorkerExit方法,如果是非例外情況,就對當前執行緒池狀態(RUNNING,shutdown)和當前作業執行緒數和當前任務數做判斷,是否要加入一個新的執行緒去完成最后的任務(防止沒有執行緒去做剩下的任務).

那么什么時候會退出while回圈呢?取不到任務的時候(getTask() == null)

/java/util/concurrent/ThreadPoolExecutor.java:1127
final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {...執行任務...}
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }




private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            //(rs == SHUTDOWN && workQueue.isEmpty()) || rs >=STOP
            //若執行緒池狀態是SHUTDOWN 并且 任務佇列為空,意味著已經不需要作業執行緒執行任務了,執行緒池即將關閉
            //若執行緒池的狀態是 STOP TIDYING TERMINATED,則意味著執行緒池已經停止處理任何任務了,不在需要執行緒
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                    //把此作業執行緒從執行緒池中洗掉
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            //allowCoreThreadTimeOut:當沒有任務的時候,核心執行緒數也會被剔除,默認引數是false,官方推薦在創建執行緒池并且還未使用的時候,設定此值
            //如果當前作業執行緒數 大于 核心執行緒數,timed為true
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
                        
            //(wc > maximumPoolSize || (timed && timedOut)):當作業執行緒超過最大執行緒數,或者 允許超時并且超時過一次了
            //(wc > 1 || workQueue.isEmpty()):作業執行緒數至少為1個 或者 沒有任務了
            //總的來說判斷當前作業執行緒還有沒有必要等著拿任務去執行
            //wc > maximumPoolSize && wc>1 : 就是判斷當前作業執行緒是否超過最大值
            //或者 wc > maximumPoolSize && workQueue.isEmpty():作業執行緒超過最大,基本上不會走到這,
            //                如果走到這,則意味著wc=1 ,只有1個作業執行緒了,如果此時任務佇列是空的,則把最后的執行緒洗掉
            //或者(timed && timedOut) && wc>1:如果允許超時并且超時過一次,并且至少有1個執行緒,則洗掉執行緒
            //或者 (timed && timedOut) && workQueue.isEmpty():如果允許超時并且超時過一次,并且此時作業                                        佇列為空,那么妥妥可以把最后一個執行緒(因為上面的wc>1不滿足,則可以得出來wc=1)洗掉
            if ((wc > maximumPoolSize  || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                        //如果減去作業執行緒數成功,則回傳null出去,也就是說 讓作業執行緒停止while輪訓,進行收尾
                    return null;
                continue;
            }

            try {
                    //判斷是否要阻塞獲取任務
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

18、AQS的理解

1、ReentrantLock和AQS的關系

java并發包下的很多API都是基于AQS來實作加鎖和釋放等功能,AQS是并java發包的基礎類,

舉個列子,ReentrantLock、ReentrantReadWr

iteLock底層都是基于AQS來實作的,ReentrantLock內部包含已個AQS物件,

AQS的全稱是AbstractQueueSynchonizer,抽象佇列同步鎖,

2、ReentrantLock加鎖和釋放鎖的底層原理

如果現在有一個執行緒過來嘗試用ReentrantLock的lock()方法進行加鎖,會發生什么?

很簡單,這個AQS物件內部有一個核心的變數state,是int型別的,代表加鎖的狀態,初始情況下為0.

另外AQS內部還有一個關鍵變數,用來記錄加鎖執行緒是哪個執行緒,初始化狀態下,這個執行緒是null,

[MISSING IMAGE: image-20220324164210432, image-20220324164210432 ]

接著執行緒1跑過來會呼叫ReentrantLock的lock()方法嘗試加鎖,這個加鎖的程序,是直接用CAS操作將state值進行0->1的,如果之間前沒有人加過鎖,那么state為0,此時執行緒1加鎖成功

一旦執行緒加鎖成功后,就可以設定當前執行緒就是自己,

下圖就是執行緒1的加鎖程序

其實到這就知道了AQS就是并發包里的一個核心組件,里面有state變數,加鎖 執行緒變數等核心東西,維護了加鎖狀態,你會發現ReentrantLock就是一個外面的API,內部的核心鎖機制都是依賴AQS組件的

這個ReentrantLock之所以以Reentrant開頭,意思是它可以可重入鎖,

可重入鎖的意思是,就是你可以對ReentrantLock物件多次執行lock()加鎖和unlock()釋放鎖,也就是可以對一個鎖加多次,叫做可重入加鎖

明白這個后看這個state變數,其實每次執行緒1可重入加鎖一次,那么他會判斷當前執行緒就是自己,那么他自己就可以沖重入多次加鎖,每次都state+1,別的沒有變化,

執行緒1加鎖完成后,那么執行緒2跑來加鎖會發生什么呢?

我們看看互斥鎖怎么實作的,執行緒2跑來發現state不是0,所以CAS重0->1就失敗,因為不為0說明被加鎖鎖了,那么就會去看當前加鎖執行緒是否是自己,不是的話自己就加鎖失敗,

看圖示意:

[MISSING IMAGE: image-20220324170008447, image-20220324170008447 ]

接著,執行緒2就會將自己放入到AQS的一個等待佇列,因為自己嘗試加鎖失敗了,此時就要將自己放入佇列中等待,等待執行緒1釋放鎖之后,自己就可以重新嘗試加鎖了,

能夠看到,AQS是如此的核心,AQS內部還有一個等待u佇列,專門放哪些加鎖失敗的執行緒,

[MISSING IMAGE: image-20220324170416331, image-20220324170416331 ]

接著,執行緒1在執行完自己的業務后,就會釋放鎖!他釋放鎖的程序很簡單,就是將AQS內部的state變數值遞減到,將“加鎖執行緒”也是設定為null,徹底釋放鎖了,

接下來,會從等待佇列中喚醒對頭的執行緒2,執行緒2重新嘗試加鎖,還是用CAS將state變為1,當前執行緒為自己執行緒,同時執行緒2自己就可以出隊了,

19、執行緒創建的三種方式

  • 1、Thread類

是Java中表示執行緒的類,里面包含了一個執行緒運行程序中方法run()方法

使用Thread類創建并啟動執行緒的步驟:

1.寫一個類,繼承Thread類

2.覆寫Thread類中的run()方法

3.創建執行緒的實體物件

4.呼叫執行緒的實體物件的start()方法去啟動執行緒

注意:

1.啟動執行緒呼叫start()方法

2.啟動執行緒之后會自動呼叫run()方法

3.需要執行緒完成某件事情,將對應的代碼添加到run()方法中即可

  • 2、實作Runnale介面(推薦使用)

步驟:

  1. 寫一個類實作Runnable介面

  2. 實作Runnable介面中的run()

  3. 創建Thread類創建物件,并將第三步創建的物件作為引數傳遞到構造方法中

  4. 呼叫Thread創建物件的start()方法,啟動執行緒

采用匿名內部類方式創建執行緒(固定格式)

public static void main(String[] args) {
    Thread th = new Thread() {
        public void run() {
        System.out.println("匿名內部類的run方法");
        };
     };
    th.start();
}
  • 3、實作Callable介面,并與future

  • 4、執行緒池結合使用

20、為什么啟動start(),就呼叫run方法

關注原始碼可以發現,在start()方法中,默認呼叫了一個JNI方法,這個方法是java平臺用于和本地C代碼進行相互操作的API

21、執行緒的生命周期

執行緒的生命周期就是執行緒的狀態

  • 1新建狀態 new

當使用new關鍵字創建執行緒實體后,該執行緒就屬于新建狀態,但是不會執行

  • 2、就緒狀態Runnable

當呼叫start()方法時,該執行緒處于就緒狀態,表示可以執行,但是不一定會立即執行,而是等待cpu

分配時間片進行處理

  • 3、運行狀態(Running)

當為該執行緒分配到時間片后,執行該方法的run方法,就處于運行狀態

  • 4、暫停狀態(包括休眠、等待、阻塞等)(Block)

當執行緒呼叫sleep()方法,主動放棄CPU資源,或者執行緒吊用阻塞IO方法時,比如控制臺的Scannner輸入方法

  • 死亡狀態(dead)

當執行緒的run()方法執行完成之后就處于死亡狀態

注意:

1.當執行緒創建時,并不會立即執行,需要呼叫start方法,使其處于就緒狀態

2.執行緒處于就緒狀態時,也不會立即執行執行緒,需要等待CPU分配時間

3.當執行緒阻塞時,會讓出占有的CPU,當阻塞結束時,執行緒會進入就緒狀態,重新等待CPU,而不是直接進入到運行狀態

  • Thread.yield():

讓出當前CPU時間片,該執行緒會從運行狀態進入到就緒狀態,此時繼續與其他執行緒搶占CPU

  • Thread.sleep(time):

讓執行緒休眠time毫秒,該執行緒會從運行狀態進入到阻塞狀態,不會與其他執行緒搶占CPU,當time毫秒過后,該執行緒會從阻塞狀態進入到就緒狀態,重新與其他執行緒搶占CPU

異步的效率會比同步的高,但是異步存在資料安全問題

多執行緒并發執行,也就是執行緒異步處理,并發執行存在執行緒安全問題

21、執行緒安全:

在實際開發中,使用多執行緒程式的情況很多,如銀行排號系統、火車站售票系統等,這種多執行緒的程式通常會發生問題,以火車站售票系統為例,在代碼中判斷當前票數是否大于0,如果大于0則執行將該票出售給乘客的功能,但當兩個執行緒同時訪問這段代碼時(假如這時只剩下一張票),第一個執行緒將票售出,與此同時第二個執行緒也已經執行完成判斷 是否有票的操作, 并得出結論票數大于0,于是它也執行售出操作,這樣就會產生負數,所以在撰寫多執行緒程式時,應該考慮到錢程安全問題,實質上執行緒安全問題來源于兩個執行緒同時存取單一物件的資料,

執行緒安全解決問題方案:

1、互斥阻塞同步:也就是加鎖sychronized和ReenrtrantLock,加鎖優缺點?

22、執行緒同步機制

為了避免多執行緒的安全問題,需要在公共訪問的內容上加鎖,加鎖之后,當一個執行緒執行該內容時,其他執行緒無法執行該內容,只有當該執行緒將此部分內容執行完了之后,其他執行緒才可以執行,

  • 1.找到多執行緒公共執行的內容
  • 2.在此內容上合適的位置加上鎖

鎖:

1、****synchronized可以加在方法上,也可以加在代碼塊中

加在方法上,在回傳值前面加synchronized既可,

比如:public synchronized void run() {}表示給run方法整體加上了鎖,
加在代碼塊上:
synchronized(this) {
//需要同步執行的代碼
}

注意:加鎖之后,被加鎖的代碼就變成了同步,會影響效率,所以應該盡量減小加鎖的范圍

2、也可以用RantantLock

23、run()方法和sart()方法有什么區別

run()方法是執行緒的執行體,他的方法代表執行緒需要完成的任務,而start()方法用來啟動執行緒,

24、執行緒是否可以被重復啟動

25、volatile

26、java多執行緒之間的三種通信方式

1、synchronized來保證執行緒安全

如果執行緒之間是通過synchronized來保證執行緒安全,則可以利用wait()、notify()、notifyAll()來實作通信

2、通過Lock()

如果執行緒之間是通過Lock()來保證執行緒安全的,則可以利用await()、signal()、signalAll()來說實作執行緒通信

這三個方法都是Condition介面中的方法,

3、BlockingQueue

jdk1.5中提供了BlockingQueue介面,雖然四Queue的子介面,但是主要用途并不是作為容器,而是作為執行緒的通信工具,BlockingQueue具有一個特征:當生產者執行緒試圖向BlockingQueue中放入一個元素,如果該佇列已滿,則該執行緒阻塞;

27、說一說synchronized的底層實作原理

一、synchronized作用在代碼塊時,它的底層是通過monitorenter、monitorexit指令來實作的,

  • *monitorenter:

每個物件都是一個監視器鎖(monitor),當monitor被占用時就會處于鎖定狀態,執行緒執行monitorenter指令時嘗試獲取monitor的所有權,程序如下:

如果monitor的進入數為0,則該執行緒進入monitor,然后將進入數設定為1,該執行緒即為monitor的所有者,如果執行緒已經占有該monitor,只是重新進入,則進入monitor的進入數加1,如果其他執行緒已經占用了monitor,則該執行緒進入阻塞狀態,直到monitor的進入數為0,再重新嘗試獲取monitor的所有權,

  • *monitorexit:

執行monitorexit的執行緒必須是objectref所對應的monitor持有者,指令執行時,monitor的進入數減1,如果減1后進入數為0,那執行緒退出monitor,不再是這個monitor的所有者,其他被這個monitor阻塞的執行緒可以嘗試去獲取這個monitor的所有權,

monitorexit指令出現了兩次,第1次為同步正常退出釋放鎖,第2次為發生異步退出釋放鎖,

二、方法的同步并沒有通過 monitorenter 和 monitorexit 指令來完成,不過相對于普通方法,其常量池中多了 ACC_SYNCHRONIZED 標示符,JVM就是根據該標示符來實作方法的同步的:

當方法呼叫時,呼叫指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標志是否被設定,如果設定了,執行執行緒將先獲取monitor,獲取成功之后才能執行方法體,方法執行完后再釋放monitor,在方法執行期間,其他任何執行緒都無法再獲得同一個monitor物件,

三、總結

兩種同步方式本質上沒有區別,只是方法的同步是一種隱式的方式來實作,無需通過位元組碼來完成,兩個指令的執行是JVM通過呼叫作業系統的互斥原語mutex來實作,被阻塞的執行緒會被掛起、等待重新調度,會導致“用戶態和內核態”兩個態之間來回切換,對性能有較大影響

28、CAS

1、概念

2、CAS可能產生ABA問題:

   ABA解決問題:加一個版本號

   版本號:數值型或者布爾型

29、鎖升級初步

  • new->偏向鎖->輕量級鎖(無鎖、自旋鎖、自適應自旋3)->重量級鎖

1、偏向鎖:

在鎖物件的物件頭中記錄?下當前獲取到該鎖的執行緒ID,該執行緒下次如果?來獲取該鎖就

2、輕量級鎖

由偏向鎖升級?來,當?個執行緒獲取到鎖后,此時這把鎖是偏向鎖,此時如果有第?個 執行緒來競爭鎖,偏向鎖就會升級為輕量級鎖,之所以叫輕量級鎖,是為了和重量級鎖區分開來,輕 量級鎖底層是通過?旋來實作的,并不會阻塞執行緒

3、鎖重入鎖

sychnronized必須記錄重入次數,因為要解鎖必須對應次數

偏向鎖 自旋鎖 ->執行緒堆疊->LR+1

4、自旋鎖什么時候升級為重量級鎖

競爭加劇:有執行緒超過10次,或者自旋鎖執行緒數超過CPU核數的一半,1.6以后加入自適應自旋,JVM自己控

5、為什么有自旋鎖還需要重量級鎖

自旋是消耗CPU資源的,如果時間過長或者自旋執行緒數多,CPU會被大量消耗

重量級鎖有等待佇列,所有拿不到鎖的進入等待佇列,不需要消耗CPU資源

6、偏向鎖是否一定比自旋鎖效率高

不一定,在明確知道會有多執行緒競爭的情況下,偏向鎖肯定會涉及鎖撤銷,這時候直接使用自旋鎖

JVM啟動程序,會有很多執行緒競爭(明確),所以默認情況下啟動是不會啟動偏向鎖,過一會時間再打開

30、ThreadLocal機制

對于ThreadLocal而言,常用的方法,就是get/set/initialValue方法,

ThreadLocal提供一個執行緒(Thread)區域變數,訪問到某個變數的每一個執行緒都擁有自己的區域變數,說白了,ThreadLocal就是想在多執行緒環境下去保證成員變數的安全,

你會看到,set需要首先獲得當前執行緒物件Thread;

然后取出當前執行緒物件的成員變數ThreadLocalMap;

如果ThreadLocalMap存在,那么進行KEY/VALUE設定,KEY就是ThreadLocal;

如果ThreadLocalMap沒有,那么創建一個;

說白了,當前執行緒中存在一個Map變數,KEY是ThreadLocal,VALUE是你設定的值,

31、ThreadLocal機制的記憶體泄露

首先來說,如果把ThreadLocal置為null,那么意味著Heap中的ThreadLocal實體不在有強參考指向,只有弱參考存在,因此GC是可以回收這部分空間的,也就是key是可以回收的,但是value卻存在一條從Current Thread過來的強參考鏈,因此只有當Current Thread銷毀時,value才能得到釋放,

因此,只要這個執行緒物件被gc回收,就不會出現記憶體泄露,但在threadLocal設為null和執行緒結束這段時間內不會被回收的,就發生了我們認為的記憶體泄露,最要命的是執行緒物件不被回收的情況,比如使用執行緒池的時候,執行緒結束是不會銷毀的,再次使用的,就可能出現記憶體泄露,

那么如何有效的避免呢?

事實上,在ThreadLocalMap中的set/getEntry方法中,會對key為null(也即是ThreadLocal為null)進行判斷,如果為null的話,那么是會對value置為null的,我們也可以通過呼叫ThreadLocal的remove方法進行釋放!

留言:

這是本人今年春招找實習作業準備總結,記錄在此,如有需要的老鐵可以看看,如有問題可以留言指導

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/458375.html

標籤:其他

上一篇:二叉樹與堆

下一篇:GPUImage – 調節影像顏色 GPUImageToneCurveFilter

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more