亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

深入理解 Java、Kotlin、Go 的線程和協(xié)程

 更新時間:2020年12月12日 10:32:43   作者:Yano_nankai  
這篇文章主要介紹了深入理解 Java、Kotlin、Go 的線程和協(xié)程,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下

前言

Go 語言比 Java 語言性能優(yōu)越的一個原因,就是輕量級線程Goroutines(協(xié)程Coroutine)。本篇文章深入分析下 Java 的線程和 Go 的協(xié)程。

協(xié)程是什么

協(xié)程并不是 Go 提出來的新概念,其他的一些編程語言,例如:Go、Python 等都可以在語言層面上實(shí)現(xiàn)協(xié)程,甚至是 Java,也可以通過使用擴(kuò)展庫來間接地支持協(xié)程。

當(dāng)在網(wǎng)上搜索協(xié)程時,我們會看到:

  • Kotlin 官方文檔說「本質(zhì)上,協(xié)程是輕量級的線程」。
  • 很多博客提到「不需要從用戶態(tài)切換到內(nèi)核態(tài)」、「是協(xié)作式的」等等。

「協(xié)程 Coroutines」源自 Simula 和 Modula-2 語言,這個術(shù)語早在 1958 年就被 Melvin Edward Conway 發(fā)明并用于構(gòu)建匯編程序,說明協(xié)程是一種編程思想,并不局限于特定的語言。

協(xié)程的好處

性能比 Java 好很多,甚至代碼實(shí)現(xiàn)都比 Java 要簡潔很多。

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

說明:下面關(guān)于進(jìn)程和線程的部分,幾乎完全參考自:http://chabaoo.cn/article/114368.htm 

進(jìn)程

進(jìn)程是什么

計(jì)算機(jī)的核心是 CPU,執(zhí)行所有的計(jì)算任務(wù);操作系統(tǒng)負(fù)責(zé)任務(wù)的調(diào)度、資源的分配和管理;應(yīng)用程序是具有某種功能的程序,程序是運(yùn)行在操作系統(tǒng)上的。

進(jìn)程是一個具有一定獨(dú)立功能的程序在一個數(shù)據(jù)集上的一次動態(tài)執(zhí)行的過程,是操作系統(tǒng)進(jìn)行資源分配和調(diào)度的一個獨(dú)立單位,是應(yīng)用程序運(yùn)行的載體。

進(jìn)程組成

進(jìn)程由三部分組成:

  • 程序:描述進(jìn)程要完成的功能,是控制進(jìn)程執(zhí)行的指令集。
  • 數(shù)據(jù)集合:程序在執(zhí)行時所需要的數(shù)據(jù)和工作區(qū)。
  • 進(jìn)程控制塊:(Program Control Block,簡稱PCB),包含進(jìn)程的描述信息和控制信息,是進(jìn)程存在的唯一標(biāo)志。

進(jìn)程特征

  • 動態(tài)性:進(jìn)程是程序的一次執(zhí)行過程,是臨時的,有生命期的,是動態(tài)產(chǎn)生,動態(tài)消亡的。
  • 并發(fā)性:任何進(jìn)程都可以同其他進(jìn)程一起并發(fā)執(zhí)行。
  • 獨(dú)立性:進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個獨(dú)立單位。結(jié)構(gòu)性:進(jìn)程由程序、數(shù)據(jù)和進(jìn)程控制塊三部分組成。

線程

線程是什么

線程是程序執(zhí)行中一個單一的順序控制流程,是程序執(zhí)行流的最小單元,是處理器調(diào)度和分派的基本單位。一個進(jìn)程可以有一個或多個線程,各個線程之間共享程序的內(nèi)存空間(也就是所在進(jìn)程的內(nèi)存空間)。

線程組成線程ID、當(dāng)前指令指針(PC)寄存器堆棧

任務(wù)調(diào)度

大部分操作系統(tǒng)(如Windows、Linux)的任務(wù)調(diào)度是采用時間片輪轉(zhuǎn)的搶占式調(diào)度方式。

在一個進(jìn)程中,當(dāng)一個線程任務(wù)執(zhí)行幾毫秒后,會由操作系統(tǒng)的內(nèi)核(負(fù)責(zé)管理各個任務(wù))進(jìn)行調(diào)度,通過硬件的計(jì)數(shù)器中斷處理器,讓該線程強(qiáng)制暫停并將該線程的寄存器放入內(nèi)存中,通過查看線程列表決定接下來執(zhí)行哪一個線程,并從內(nèi)存中恢復(fù)該線程的寄存器,最后恢復(fù)該線程的執(zhí)行,從而去執(zhí)行下一個任務(wù)。

進(jìn)程與線程的區(qū)別

線程是程序執(zhí)行的最小單位,而進(jìn)程是操作系統(tǒng)分配資源的最小單位;一個進(jìn)程由一個或多個線程組成,線程是一個進(jìn)程中代碼的不同執(zhí)行路線;進(jìn)程之間相互獨(dú)立,但同一進(jìn)程下的各個線程之間共享程序的內(nèi)存空間(包括代碼段、數(shù)據(jù)集、堆等)及一些進(jìn)程級的資源(如打開文件和信號),某進(jìn)程內(nèi)的線程在其它進(jìn)程不可見;調(diào)度和切換:線程上下文切換進(jìn)程上下文切換得多。

線程的實(shí)現(xiàn)模型

程序一般不會直接去使用內(nèi)核線程,而是去使用內(nèi)核線程的一種高級接口——輕量級進(jìn)程(Lightweight Process,LWP),輕量級進(jìn)程就是我們通常意義上所講的線程,也被叫做用戶線程。

一對一模型

一個用戶線程對應(yīng)一個內(nèi)核線程,如果是多核的 CPU,那么線程之間是真正的并發(fā)。

缺點(diǎn):

  • 內(nèi)核線程的數(shù)量有限,一對一模型使用的用戶線程數(shù)量有限制。
  • 內(nèi)核線程的調(diào)度,上下文切換的開銷較大(雖然沒有進(jìn)程上下文切換的開銷大),導(dǎo)致用戶線程的執(zhí)行效率下降。

多對一模型

多個用戶線程映射到一個內(nèi)核線程上,線程間的切換由用戶態(tài)的代碼來進(jìn)行。用戶線程的建立、同步、銷毀都是在用戶態(tài)中完成,不需要內(nèi)核的介入。因此多對一的上下文切換速度快很多,且用戶線程的數(shù)量幾乎沒有限制。

缺點(diǎn):

  • 若一個用戶線程阻塞,其他所有線程都無法執(zhí)行,此時內(nèi)核線程處于阻塞狀態(tài)。
  • 處理器數(shù)量的增加,不會對多對一模型的線程性能造成影響,因?yàn)樗械挠脩艟€程都映射到了一個處理器上。

多對多模型

結(jié)合了一對一模型多對一模型的優(yōu)點(diǎn),多個用戶線程映射到多個內(nèi)核線程上,由線程庫負(fù)責(zé)在可用的可調(diào)度實(shí)體上調(diào)度用戶線程。這樣線程間的上下文切換很快,因?yàn)樗苊饬讼到y(tǒng)調(diào)用。但是增加了系統(tǒng)的復(fù)雜性。

優(yōu)點(diǎn):

一個用戶線程的阻塞不會導(dǎo)致所有線程的阻塞,因?yàn)榇藭r還有別的內(nèi)核線程被調(diào)度來執(zhí)行;多對多模型對用戶線程的數(shù)量沒有限制;在多處理器的操作系統(tǒng)中,多對多模型的線程也能得到一定的性能提升,但提升的幅度不如一對一模型的高。

線程的“并發(fā)”

只有在線程的數(shù)量 < 處理器的數(shù)量時,線程的并發(fā)才是真正的并發(fā),這時不同的線程運(yùn)行在不同的處理器上。但是當(dāng)線程的數(shù)量 > 處理器的數(shù)量時,會出現(xiàn)一個處理器運(yùn)行多個線程的情況。

在單個處理器運(yùn)行多個線程時,并發(fā)是一種模擬出來的狀態(tài)。操作系統(tǒng)采用時間片輪轉(zhuǎn)的方式輪流執(zhí)行每一個線程?,F(xiàn)在,幾乎所有的現(xiàn)代操作系統(tǒng)采用的都是時間片輪轉(zhuǎn)的搶占式調(diào)度方式。

協(xié)程

當(dāng)在網(wǎng)上搜索協(xié)程時,我們會看到:

本質(zhì)上,協(xié)程是輕量級的線程。很多博客提到「不需要從用戶態(tài)切換到內(nèi)核態(tài)」、「是協(xié)作式的」。

協(xié)程也并不是 Go 提出來的,協(xié)程是一種編程思想,并不局限于特定的語言。Go、Python、Kotlin 都可以在語言層面上實(shí)現(xiàn)協(xié)程,Java 也可以通過擴(kuò)展庫的方式間接支持協(xié)程。

協(xié)程比線程更加輕量級,可以由程序員自己管理的輕量級線程,對內(nèi)核不可見。

協(xié)程的目的

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

最常見的例子就是 JDBC(它是同步阻塞的),這也是為什么很多人都說數(shù)據(jù)庫是瓶頸的原因。這里的耗時其實(shí)是讓 CPU 一直在等待 I/O 返回,說白了線程根本沒有利用 CPU 去做運(yùn)算,而是處于空轉(zhuǎn)狀態(tài)。而另外過多的線程,也會帶來更多的 ContextSwitch 開銷。

對于上述問題,現(xiàn)階段行業(yè)里的比較流行的解決方案之一就是單線程加上異步回調(diào)。其代表派是 node.js 以及 Java 里的新秀 Vert.x。

而協(xié)程的目的就是當(dāng)出現(xiàn)長時間的 I/O 操作時,通過讓出目前的協(xié)程調(diào)度,執(zhí)行下一個任務(wù)的方式,來消除 ContextSwitch 上的開銷。

協(xié)程的特點(diǎn)線程的切換由操作系統(tǒng)負(fù)責(zé)調(diào)度,協(xié)程由用戶自己進(jìn)行調(diào)度,減少了上下文切換,提高了效率線程的默認(rèn) Stack 是1M,協(xié)程更加輕量,是 1K,在相同內(nèi)存中可以開啟更多的協(xié)程。由于在同一個線程上,因此可以避免競爭關(guān)系而使用鎖。適用于被阻塞的,且需要大量并發(fā)的場景。但不適用于大量計(jì)算的多線程,遇到此種情況,更好用線程去解決。

協(xié)程的原理

當(dāng)出現(xiàn)IO阻塞的時候,由協(xié)程的調(diào)度器進(jìn)行調(diào)度,通過將數(shù)據(jù)流立刻yield掉(主動讓出),并且記錄當(dāng)前棧上的數(shù)據(jù),阻塞完后立刻再通過線程恢復(fù)棧,并把阻塞的結(jié)果放到這個線程上去跑,這樣看上去好像跟寫同步代碼沒有任何差別,這整個流程可以稱為coroutine,而跑在由coroutine負(fù)責(zé)調(diào)度的線程稱為Fiber。比如Golang里的 go關(guān)鍵字其實(shí)就是負(fù)責(zé)開啟一個Fiber,讓func邏輯跑在上面。

由于協(xié)程的暫停完全由程序控制,發(fā)生在用戶態(tài)上;而線程的阻塞狀態(tài)是由操作系統(tǒng)內(nèi)核來進(jìn)行切換,發(fā)生在內(nèi)核態(tài)上。
因此,協(xié)程的開銷遠(yuǎn)遠(yuǎn)小于線程的開銷,也就沒有了 ContextSwitch 上的開銷。

假設(shè)程序中默認(rèn)創(chuàng)建兩個線程為協(xié)程使用,在主線程中創(chuàng)建協(xié)程ABCD…,分別存儲在就緒隊(duì)列中,調(diào)度器首先會分配一個工作線程A執(zhí)行協(xié)程A,另外一個工作線程B執(zhí)行協(xié)程B,其它創(chuàng)建的協(xié)程將會放在隊(duì)列中進(jìn)行排隊(duì)等待。

當(dāng)協(xié)程A調(diào)用暫停方法或被阻塞時,協(xié)程A會進(jìn)入到掛起隊(duì)列,調(diào)度器會調(diào)用等待隊(duì)列中的其它協(xié)程搶占線程A執(zhí)行。當(dāng)協(xié)程A被喚醒時,它需要重新進(jìn)入到就緒隊(duì)列中,通過調(diào)度器搶占線程,如果搶占成功,就繼續(xù)執(zhí)行協(xié)程A,失敗則繼續(xù)等待搶占線程。

Java、Kotlin、Go 的線程與協(xié)程

Java 在 Linux 操作系統(tǒng)下使用的是用戶線程+輕量級線程,一個用戶線程映射到一個內(nèi)核線程,線程之間的切換就涉及到了上下文切換。所以在 Java 中并不適合創(chuàng)建大量的線程,否則效率會很低??梢韵瓤聪?Kotlin 和 Go 的協(xié)程:

Kotlin 的協(xié)程

Kotlin 在誕生之初,目標(biāo)就是完全兼容 Java,卻是一門非常務(wù)實(shí)的語言,其中一個特性,就是支持協(xié)程。

但是 Kotlin 最終還是運(yùn)行在 JVM 中的,目前的 JVM 并不支持協(xié)程,Kotlin 作為一門編程語言,也只是能在語言層面支持協(xié)程。Kotlin 的協(xié)程是用于異步編程等場景的,在語言級提供協(xié)程支持,而將大部分功能委托給庫。

使用「線程」的代碼

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

上述代碼創(chuàng)建了 100 萬個線程,在每個線程里僅僅調(diào)用了 add 操作,但是由于創(chuàng)建線程太多,這個測試用例在我的機(jī)器上要跑 1 分鐘左右。

使用「協(xié)程」的代碼

@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

這段代碼是創(chuàng)建了 100 萬個協(xié)程,測試用例在我的機(jī)器上執(zhí)行時間大概是 10 秒鐘。而且這段代碼的每個協(xié)程都 delay 了 1 秒鐘,執(zhí)行效率仍然遠(yuǎn)遠(yuǎn)高于線程。

詳細(xì)的語法可以查看 Kotlin 的官方網(wǎng)站:https://www.kotlincn.net/docs/reference/coroutines/basics.html

其中關(guān)鍵字 launch 是開啟了一個協(xié)程,關(guān)鍵字 suspend 是掛起一個協(xié)程,而不會阻塞?,F(xiàn)在在看這個流程,應(yīng)該就懂了~

Go 的協(xié)程

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

go語言層面并不支持多進(jìn)程或多線程,但是協(xié)程更好用,協(xié)程被稱為用戶態(tài)線程,不存在CPU上下文切換問題,效率非常高。下面是一個簡單的協(xié)程演示代碼:

package main

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

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

Java 的 Kilim 協(xié)程框架

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

Java 的 Project Loom

Java 也在逐步支持協(xié)程,其項(xiàng)目就是 Project Loom(https://openjdk.java.net/projects/loom/)。這個項(xiàng)目在18年底的時候已經(jīng)達(dá)到可初步演示的原型階段。不同于之前的方案,Project Loom 是從 JVM 層面對多線程技術(shù)進(jìn)行徹底的改變。

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

其中一段介紹了為什么引入這個項(xiàng)目:

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 的用戶線程與內(nèi)核線程是一對一的關(guān)系,一個 Java 進(jìn)程很難創(chuàng)建上千個線程,如果是對于 I/O 阻塞的程序(例如數(shù)據(jù)庫讀取/Web服務(wù)),性能會很低下,所以要采用類似于協(xié)程的機(jī)制。

使用 Fiber

在引入 Project Loom 之后,JDK 將引入一個新類:java.lang.Fiber。此類與 java.lang.Thread 一起,都成為了 java.lang.Strand 的子類。即線程變成了一個虛擬的概念,有兩種實(shí)現(xiàn)方法:Fiber 所表示的輕量線程和 Thread 所表示的傳統(tǒng)的重量級線程。

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

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

總結(jié)

協(xié)程大法好,比線程更輕量級,但是僅針對 I/O 阻塞才有效;對于 CPU 密集型的應(yīng)用,因?yàn)?CPU 一直都在計(jì)算并沒有什么空閑,所以沒有什么作用。

Kotlin 兼容 Java,在編譯器、語言層面實(shí)現(xiàn)了協(xié)程,JVM 底層并不支持協(xié)程;Go 天生就是支持協(xié)程的,不支持多進(jìn)程和多線程。Java 的 Project Loom 項(xiàng)目支持協(xié)程,

參考資料

極客時間-Java性能調(diào)優(yōu)實(shí)戰(zhàn)/19.如何用協(xié)程來優(yōu)化多線程業(yè)務(wù)?

https://www.cnblogs.com/Survivalist/p/11527949.html

https://www.jianshu.com/p/5db701a764cb

到此這篇關(guān)于深入理解 Java、Kotlin、Go 的線程和協(xié)程的文章就介紹到這了,更多相關(guān) Java、Kotlin、Go 的線程和協(xié)程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 如何通過zuul添加或修改請求參數(shù)

    如何通過zuul添加或修改請求參數(shù)

    這篇文章主要介紹了如何通過zuul添加或修改請求參數(shù)的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • SpringBoot使用PageHelper插件實(shí)現(xiàn)Mybatis分頁效果

    SpringBoot使用PageHelper插件實(shí)現(xiàn)Mybatis分頁效果

    這篇文章主要介紹了SpringBoot使用PageHelper插件實(shí)現(xiàn)Mybatis分頁效果,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的參考借鑒價值,需要的朋友可以參考下
    2024-02-02
  • 詳解spring boot容器加載完后執(zhí)行特定操作

    詳解spring boot容器加載完后執(zhí)行特定操作

    這篇文章主要介紹了詳解spring boot容器加載完后執(zhí)行特定操作,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-01-01
  • Java 十大排序算法之堆排序刨析

    Java 十大排序算法之堆排序刨析

    堆排序是利用堆這種數(shù)據(jù)結(jié)構(gòu)而設(shè)計(jì)的一種排序算法,堆排序是一種選擇排序,它的最壞,最好,平均時間復(fù)雜度均為O(nlogn),它也是不穩(wěn)定排序。首先簡單了解下堆結(jié)構(gòu)
    2021-11-11
  • java中的三種取整函數(shù)總結(jié)

    java中的三種取整函數(shù)總結(jié)

    下面小編就為大家?guī)硪黄猨ava中的三種取整函數(shù)總結(jié)。希望對大家有所幫助。一起跟隨小編過來看看吧,祝大家游戲愉快哦
    2016-11-11
  • JAVA基于PDF box將PDF轉(zhuǎn)為圖片的實(shí)現(xiàn)方法

    JAVA基于PDF box將PDF轉(zhuǎn)為圖片的實(shí)現(xiàn)方法

    這篇文章主要介紹了JAVA基于PDF box將PDF轉(zhuǎn)為圖片的操作方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值 ,需要的朋友可以參考下
    2019-07-07
  • SVN出現(xiàn)提示org.apache.subversion.javahl.ClientException: Attempted to lock an already-locked dir解決方案

    SVN出現(xiàn)提示org.apache.subversion.javahl.ClientException: Attempt

    這篇文章主要介紹了SVN出現(xiàn)提示org.apache.subversion.javahl.ClientException: Attempted to lock an already-locked dir解決方案的相關(guān)資料,需要的朋友可以參考下
    2016-12-12
  • spark中使用groupByKey進(jìn)行分組排序的示例代碼

    spark中使用groupByKey進(jìn)行分組排序的示例代碼

    這篇文章主要介紹了spark中使用groupByKey進(jìn)行分組排序的實(shí)例代碼,本文通過實(shí)例代碼給大家講解的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-03-03
  • 關(guān)于Spring?Ioc和DI注解的問題

    關(guān)于Spring?Ioc和DI注解的問題

    這篇文章主要介紹了Spring?Ioc和DI注解,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-03-03
  • Java文件寫入器FileWriter使用指南

    Java文件寫入器FileWriter使用指南

    在Java中,FileWriter類用于將字符寫入文件中,它繼承了Writer類,因此可以使用Writer類中的所有方法,下面我們就來深入探討一下FileWriter類的使用方法吧
    2023-10-10

最新評論