主頁 > 後端開發 > 深入分析 Java、Kotlin、Go 的執行緒和協程

深入分析 Java、Kotlin、Go 的執行緒和協程

2020-12-12 06:50:58 後端開發

  • 前言
    • 協程是什么
    • 協程的好處
  • 行程
    • 行程是什么
    • 行程組成
    • 行程特征
  • 執行緒
    • 執行緒是什么
    • 執行緒組成
    • 任務調度
    • 行程與執行緒的區別
    • 執行緒的實作模型
      • 一對一模型
      • 多對一模型
      • 多對多模型
    • 執行緒的“并發”
  • 協程
    • 協程的目的
    • 協程的特點
    • 協程的原理
  • Java、Kotlin、Go 的執行緒與協程
    • Kotlin 的協程
      • 使用「執行緒」的代碼
      • 使用「協程」的代碼
    • Go 的協程
    • Java 的 Kilim 協程框架
    • Java 的 Project Loom
      • 使用 Fiber
  • 總結
  • 參考資料

前言

Go 語言比 Java 語言性能優越的一個原因,就是輕量級執行緒Goroutines(協程Coroutine),本篇文章深入分析下 Java 的執行緒和 Go 的協程,

協程是什么

協程并不是 Go 提出來的新概念,其他的一些編程語言,例如:Go、Python 等都可以在語言層面上實作協程,甚至是 Java,也可以通過使用擴展庫來間接地支持協程,

當在網上搜索協程時,我們會看到:

  • Kotlin 官方檔案說「本質上,協程是輕量級的執行緒」,
  • 很多博客提到「不需要從用戶態切換到內核態」、「是協作式的」等等,

「協程 Coroutines」源自 Simula 和 Modula-2 語言,這個術語早在 1958 年就被 Melvin Edward Conway 發明并用于構建匯編程式,說明協程是一種編程思想,并不局限于特定的語言,

協程的好處

性能比 Java 好很多,甚至代碼實作都比 Java 要簡潔很多,

那這究竟又是為什么呢?下面一一分析,

說明:下面關于行程和執行緒的部分,幾乎完全參考自:https://www.cnblogs.com/Survivalist/p/11527949.html,這篇文章寫得太好了~~~

行程

行程是什么

計算機的核心是 CPU,執行所有的計算任務;作業系統負責任務的調度、資源的分配和管理;應用程式是具有某種功能的程式,程式是運行在作業系統上的,

行程是一個具有一定獨立功能的程式在一個資料集上的一次動態執行的程序,是作業系統進行資源分配和調度的一個獨立單位,是應用程式運行的載體,

行程組成

行程由三部分組成:

  • 程式:描述行程要完成的功能,是控制行程執行的指令集,
  • 資料集合:程式在執行時所需要的資料和作業區,
  • 行程控制塊:(Program Control Block,簡稱PCB),包含行程的描述資訊和控制資訊,是行程存在的唯一標志,

行程特征

  • 動態性:行程是程式的一次執行程序,是臨時的,有生命期的,是動態產生,動態消亡的,
  • 并發性:任何行程都可以同其他行程一起并發執行,
  • 獨立性:行程是系統進行資源分配和調度的一個獨立單位,
  • 結構性:行程由程式、資料和行程控制塊三部分組成,

執行緒

執行緒是什么

執行緒是程式執行中一個單一的順序控制流程,是程式執行流的最小單元,是處理器調度和分派的基本單位,一個行程可以有一個或多個執行緒,各個執行緒之間共享程式的記憶體空間(也就是所在行程的記憶體空間),

執行緒組成

  • 執行緒ID、當前指令指標(PC)
  • 暫存器
  • 堆疊

任務調度

大部分作業系統(如Windows、Linux)的任務調度是采用時間片輪轉的搶占式調度方式

在一個行程中,當一個執行緒任務執行幾毫秒后,會由作業系統的內核(負責管理各個任務)進行調度,通過硬體的計數器中斷處理器,讓該執行緒強制暫停并將該執行緒的暫存器放入記憶體中,通過查看執行緒串列決定接下來執行哪一個執行緒,并從記憶體中恢復該執行緒的暫存器,最后恢復該執行緒的執行,從而去執行下一個任務,

行程與執行緒的區別

  • 執行緒是程式執行的最小單位,而行程是作業系統分配資源的最小單位;
  • 一個行程由一個或多個執行緒組成,執行緒是一個行程中代碼的不同執行路線
  • 行程之間相互獨立,但同一行程下的各個執行緒之間共享程式的記憶體空間(包括代碼段、資料集、堆等)及一些行程級的資源(如打開檔案和信號),某行程內的執行緒在其它行程不可見;
  • 調度和切換:執行緒背景關系切換行程背景關系切換得多,

執行緒的實作模型

程式一般不會直接去使用內核執行緒,而是去使用內核執行緒的一種高級介面——輕量級行程(Lightweight Process,LWP),輕量級行程就是我們通常意義上所講的執行緒,也被叫做用戶執行緒,

一對一模型

一個用戶執行緒對應一個內核執行緒,如果是多核的 CPU,那么執行緒之間是真正的并發,

缺點:

  • 內核執行緒的數量有限,一對一模型使用的用戶執行緒數量有限制,
  • 內核執行緒的調度,背景關系切換的開銷較大(雖然沒有行程背景關系切換的開銷大),導致用戶執行緒的執行效率下降,

多對一模型

多個用戶執行緒映射到一個內核執行緒上,執行緒間的切換由用戶態的代碼來進行,用戶執行緒的建立、同步、銷毀都是在用戶態中完成,不需要內核的介入,因此多對一的背景關系切換速度快很多,且用戶執行緒的數量幾乎沒有限制,

缺點:

  • 若一個用戶執行緒阻塞,其他所有執行緒都無法執行,此時內核執行緒處于阻塞狀態,
  • 處理器數量的增加,不會對多對一模型的執行緒性能造成影響,因為所有的用戶執行緒都映射到了一個處理器上,

多對多模型

結合了一對一模型多對一模型的優點,多個用戶執行緒映射到多個內核執行緒上,由執行緒庫負責在可用的可調度物體上調度用戶執行緒,這樣執行緒間的背景關系切換很快,因為它避免了系統呼叫,但是增加了系統的復雜性,

優點:

  • 一個用戶執行緒的阻塞不會導致所有執行緒的阻塞,因為此時還有別的內核執行緒被調度來執行;
  • 多對多模型對用戶執行緒的數量沒有限制;
  • 在多處理器的作業系統中,多對多模型的執行緒也能得到一定的性能提升,但提升的幅度不如一對一模型的高,

執行緒的“并發”

只有在執行緒的數量 < 處理器的數量時,執行緒的并發才是真正的并發,這時不同的執行緒運行在不同的處理器上,但是當執行緒的數量 > 處理器的數量時,會出現一個處理器運行多個執行緒的情況,

在單個處理器運行多個執行緒時,并發是一種模擬出來的狀態,作業系統采用時間片輪轉的方式輪流執行每一個執行緒,現在,幾乎所有的現代作業系統采用的都是時間片輪轉的搶占式調度方式,

協程

當在網上搜索協程時,我們會看到:

  • 本質上,協程是輕量級的執行緒,
  • 很多博客提到「不需要從用戶態切換到內核態」、「是協作式的」,

協程也并不是 Go 提出來的,協程是一種編程思想,并不局限于特定的語言,Go、Python、Kotlin 都可以在語言層面上實作協程,Java 也可以通過擴展庫的方式間接支持協程,

協程比執行緒更加輕量級,可以由程式員自己管理的輕量級執行緒,對內核不可見,

協程的目的

在傳統的 J2EE 系統中都是基于每個請求占用一個執行緒去完成完整的業務邏輯(包括事務),所以系統的吞吐能力取決于每個執行緒的操作耗時,如果遇到很耗時的 I/O 行為,則整個系統的吞吐立刻下降,因為這個時候執行緒一直處于阻塞狀態,如果執行緒很多的時候,會存在很多執行緒處于空閑狀態(等待該執行緒執行完才能執行),造成了資源應用不徹底,

最常見的例子就是 JDBC(它是同步阻塞的),這也是為什么很多人都說資料庫是瓶頸的原因,這里的耗時其實是讓 CPU 一直在等待 I/O 回傳,說白了執行緒根本沒有利用 CPU 去做運算,而是處于空轉狀態,而另外過多的執行緒,也會帶來更多的 ContextSwitch 開銷,

對于上述問題,現階段行業里的比較流行的解決方案之一就是單執行緒加上異步回呼,其代表派是 node.js 以及 Java 里的新秀 Vert.x,

而協程的目的就是當出現長時間的 I/O 操作時,通過讓出目前的協程調度,執行下一個任務的方式,來消除 ContextSwitch 上的開銷,

協程的特點

  • 執行緒的切換由作業系統負責調度,協程由用戶自己進行調度,減少了背景關系切換,提高了效率
  • 執行緒的默認 Stack 是1M,協程更加輕量,是 1K,在相同記憶體中可以開啟更多的協程,
  • 由于在同一個執行緒上,因此可以避免競爭關系而使用鎖,
  • 適用于被阻塞的,且需要大量并發的場景,但不適用于大量計算的多執行緒,遇到此種情況,更好用執行緒去解決,

協程的原理

當出現IO阻塞的時候,由協程的調度器進行調度,通過將資料流立刻yield掉(主動讓出),并且記錄當前堆疊上的資料,阻塞完后立刻再通過執行緒恢復堆疊,并把阻塞的結果放到這個執行緒上去跑,這樣看上去好像跟寫同步代碼沒有任何差別,這整個流程可以稱為coroutine,而跑在由coroutine負責調度的執行緒稱為Fiber,比如Golang里的 go關鍵字其實就是負責開啟一個Fiber,讓func邏輯跑在上面,

由于協程的暫停完全由程式控制,發生在用戶態上;而執行緒的阻塞狀態是由作業系統內核來進行切換,發生在內核態上,
因此,協程的開銷遠遠小于執行緒的開銷,也就沒有了 ContextSwitch 上的開銷,

假設程式中默認創建兩個執行緒為協程使用,在主執行緒中創建協程ABCD…,分別存盤在就緒佇列中,調度器首先會分配一個作業執行緒A執行協程A,另外一個作業執行緒B執行協程B,其它創建的協程將會放在佇列中進行排隊等待,

當協程A呼叫暫停方法或被阻塞時,協程A會進入到掛起佇列,調度器會呼叫等待佇列中的其它協程搶占執行緒A執行,當協程A被喚醒時,它需要重新進入到就緒佇列中,通過調度器搶占執行緒,如果搶占成功,就繼續執行協程A,失敗則繼續等待搶占執行緒,

Java、Kotlin、Go 的執行緒與協程

Java 在 Linux 作業系統下使用的是用戶執行緒+輕量級執行緒,一個用戶執行緒映射到一個內核執行緒,執行緒之間的切換就涉及到了背景關系切換,所以在 Java 中并不適合創建大量的執行緒,否則效率會很低,可以先看下 Kotlin 和 Go 的協程:

Kotlin 的協程

Kotlin 在誕生之初,目標就是完全兼容 Java,卻是一門非常務實的語言,其中一個特性,就是支持協程,

但是 Kotlin 最侄訓是運行在 JVM 中的,目前的 JVM 并不支持協程,Kotlin 作為一門編程語言,也只是能在語言層面支持協程,Kotlin 的協程是用于異步編程等場景的,在語言級提供協程支持,而將大部分功能委托給庫,

使用「執行緒」的代碼

@Test
fun testThread() {
    // 執行時間 1min+
    val c = AtomicLong()
    for (i in 1..1_000_000L)
        thread(start = true) {
            c.addAndGet(i)
        }
    println(c.get())
}

上述代碼創建了 100 萬個執行緒,在每個執行緒里僅僅呼叫了 add 操作,但是由于創建執行緒太多,這個測驗用例在我的機器上要跑 1 分鐘左右,

使用「協程」的代碼

@Test
fun testLaunch() {
    val c = AtomicLong()
    runBlocking {
        for (i in 1..1_000_000L)
            launch {
                c.addAndGet(workload(i))
            }
    }
    print(c.get())
}

suspend fun workload(n: Long): Long {
    delay(1000)
    return n
}

這段代碼是創建了 100 萬個協程,測驗用例在我的機器上執行時間大概是 10 秒鐘,而且這段代碼的每個協程都 delay 了 1 秒鐘,執行效率仍然遠遠高于執行緒,

詳細的語法可以查看 Kotlin 的官方網站:https://www.kotlincn.net/docs/reference/coroutines/basics.html

其中關鍵字 launch 是開啟了一個協程,關鍵字 suspend 是掛起一個協程,而不會阻塞,現在在看這個流程,應該就懂了~

Go 的協程

官方例程:https://gobyexample-cn.github.io/goroutines

go語言層面并不支持多行程或多執行緒,但是協程更好用,協程被稱為用戶態執行緒,不存在CPU背景關系切換問題,效率非常高,下面是一個簡單的協程演示代碼:

package main

func main() {
    go say("Hello World")
}

func say(s string) {
    println(s)
}

Java 的 Kilim 協程框架

目前 Java 原生語言暫時不支持協程,可以使用 kilim,具體原理可以看官方檔案,暫時還沒有研究~

Java 的 Project Loom

Java 也在逐步支持協程,其專案就是 Project Loom(https://openjdk.java.net/projects/loom/),這個專案在18年底的時候已經達到可初步演示的原型階段,不同于之前的方案,Project Loom 是從 JVM 層面對多執行緒技術進行徹底的改變,

官方介紹:
http://cr.openjdk.java.net/~rpressler/loom/Loom-Proposal.html

其中一段介紹了為什么引入這個專案:

One of Java's most important contributions when it was first released, over twenty years ago, was the easy access to threads and synchronization primitives. Java threads (either used directly, or indirectly through, for example, Java servlets processing HTTP requests) provided a relatively simple abstraction for writing concurrent applications. These days, however, one of the main difficulties in writing concurrent programs that meet today's requirements is that the software unit of concurrency offered by the runtime — the thread — cannot match the scale of the domain's unit of concurrency, be it a user, a transaction or even a single operation. Even if the unit of application concurrency is coarse — say, a session, represented by single socket connection — a server can handle upward of a million concurrent open sockets, yet the Java runtime, which uses the operating system's threads for its implementation of Java threads, cannot efficiently handle more than a few thousand. A mismatch in several orders of magnitude has a big impact.

文章大意就是本文上面所說的,Java 的用戶執行緒與內核執行緒是一對一的關系,一個 Java 行程很難創建上千個執行緒,如果是對于 I/O 阻塞的程式(例如資料庫讀取/Web服務),性能會很低下,所以要采用類似于協程的機制,

使用 Fiber

在引入 Project Loom 之后,JDK 將引入一個新類:java.lang.Fiber,此類與 java.lang.Thread 一起,都成為了 java.lang.Strand 的子類,即執行緒變成了一個虛擬的概念,有兩種實作方法:Fiber 所表示的輕量執行緒和 Thread 所表示的傳統的重量級執行緒,

Fiber f = Fiber.schedule(() -> {
  println("Hello 1");
  lock.lock(); // 等待鎖不會掛起執行緒
  try {
      println("Hello 2");
  } finally {
      lock.unlock();
  }
  println("Hello 3");
})

只需執行 Fiber.schedule(Runnable task) 就能在 Fiber 中執行任務,最重要的是,上面例子中的 lock.lock() 操作將不再掛起底層執行緒,除了 Lock 不再掛起執行緒以外,像 Socket BIO 操作也不再掛起執行緒, 但 synchronized,以及 Native 方法中執行緒掛起操作無法避免,

總結

協程大法好,比執行緒更輕量級,但是僅針對 I/O 阻塞才有效;對于 CPU 密集型的應用,因為 CPU 一直都在計算并沒有什么空閑,所以沒有什么作用,

Kotlin 兼容 Java,在編譯器、語言層面實作了協程,JVM 底層并不支持協程;Go 天生就是支持協程的,不支持多行程和多執行緒,Java 的 Project Loom 專案支持協程,

參考資料

  • 極客時間-Java性能調優實戰/19.如何用協程來優化多執行緒業務?
  • https://www.cnblogs.com/Survivalist/p/11527949.html
  • https://www.jianshu.com/p/5db701a764cb

公眾號

coding 筆記、點滴記錄,以后的文章也會同步到公眾號(Coding Insight)中,希望大家關注_

代碼和思維導圖在 GitHub 專案中,歡迎大家 star!

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

標籤:Java

上一篇:SpringMVC源代碼分析與實踐

下一篇:突擊學習多執行緒面試重點,阿里架構師說其實很簡單

標籤雲
其他(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