【線程的另一種形式】

今天研究的問題:

1. Go并發(fā)憂于Java并發(fā)?
2. Go語言的并發(fā)是多線程實(shí)現(xiàn)的么?
3. Java并發(fā)性能如何提高?
4. 線程的分類:
4. 如何根據(jù)場景選擇不同的線程實(shí)現(xiàn)方式?

一.線程開啟方式對比

場景1:

假設(shè)我們有一個任務(wù),平均執(zhí)行時間為1秒,分別測試一下使用線程和協(xié)程并發(fā)執(zhí)行100000次需要消耗多少時間多律。
go語言

// 線程開啟
// 線程開啟
func testThread(i int) {

    fmt.Println("當(dāng)前值:", i )
    time.Sleep(time.Second) //延時一秒
}
func test() {
    for i := 0; i < 100000; i++ {
        go testThread(i)
    }
}

func main() {
    start := time.Now() // 獲取當(dāng)前時間
    test()
    elapsed := time.Since(start)
    fmt.Println("該函數(shù)執(zhí)行完成耗時:", elapsed)
}
總耗時
image.png
java語言
public class TestThread01 extends Thread {
    private int i;

    public TestThread01(int i) {
        this.i = i;
    }

    public void run() {
        System.out.println(this.i);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        long startMili = System.currentTimeMillis();// 當(dāng)前時間對應(yīng)的毫秒數(shù)
        for (int i = 0; i < 100000; i++) {
            new TestThread01(i).start();
        }
        long endMili = System.currentTimeMillis();//結(jié)束時間
        Thread.sleep(7000);
        System.out.println("/**總耗時為:" + (endMili - startMili) + "毫秒");
    }
}
總耗時
image.png
線程池實(shí)現(xiàn)方式
@Configuration
@EnableAsync
public class BeanConfig {

    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 設(shè)置核心線程數(shù)
        executor.setCorePoolSize(10);
        // 設(shè)置最大線程數(shù)
        executor.setMaxPoolSize(100000);
        // 設(shè)置隊列容量
        executor.setQueueCapacity(10);
        // 設(shè)置線程活躍時間(秒)
        executor.setKeepAliveSeconds(10);
        // 設(shè)置默認(rèn)線程名稱
        executor.setThreadNamePrefix("hello-");
        // 設(shè)置拒絕策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任務(wù)結(jié)束后再關(guān)閉線程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        return executor;
    }
}

測試類:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = AppApplication.class)
public class TestThreadPool {
    @Autowired
    private Print print;

    @Test
    public void test() throws InterruptedException {
      
        long startMili = System.currentTimeMillis();// 當(dāng)前時間對應(yīng)的毫秒數(shù)
        for (int i = 0; i < 100000; i++) {
            print.sayHello(i);
        }
        long endMili = System.currentTimeMillis();//結(jié)束時間
        Thread.sleep(1000);
        System.out.println("/**總耗時為:" + (endMili - startMili) + "毫秒");
    }
}
總耗時:
image.png

結(jié)論:

從總體看go語言實(shí)現(xiàn)多線程的方式更為簡潔耗時更短,go關(guān)鍵字輕松實(shí)現(xiàn)
java語言實(shí)現(xiàn)線程相對復(fù)雜,耗時更長
線程池實(shí)現(xiàn)方式需要復(fù)雜配置.
代碼在執(zhí)行過程中CUP占用率: Java > Java線程池 > go語言

  • 本質(zhì)原因: go語言開啟的不是線程--->而是協(xié)程(線程中的線程)

原因分析:

分析前需要了解:進(jìn)程-線程-協(xié)程區(qū)別

進(jìn)程空間分配

操作系統(tǒng)采用虛擬內(nèi)存技術(shù)刑巧,把進(jìn)程虛擬地址空間劃分成用戶空間和內(nèi)核空間。

4GB序的進(jìn)程虛擬地址空間被分成兩部分:用戶空間和內(nèi)核空間


image.png

用戶空間

用戶空間按照訪問屬性一致的地址空間存放在一起的原則逆皮,劃分成 5個不同的內(nèi)存區(qū)域宅粥。訪問屬性指的是“可讀、可寫电谣、可執(zhí)行等 秽梅。

  1. 代碼段代碼段是用來存放可執(zhí)行文件的操作指令,可執(zhí)行程序在內(nèi)存中的鏡像剿牺。代碼段需要防止在運(yùn)行時被非法修改企垦,所以只準(zhǔn)許讀取操作,它是不可寫的晒来。
  2. 數(shù)據(jù)段數(shù)據(jù)段用來存放可執(zhí)行文件中已初始化全局變量钞诡,換句話說就是存放程序靜態(tài)分配的變量和全局變量。
  3. BSS段BSS段包含了程序中未初始化的全局變量潜索,在內(nèi)存中 bss 段全部置零臭增。
  4. 對 heap堆是用于存放進(jìn)程運(yùn)行中被動態(tài)分配的內(nèi)存段,它的大小并不固定竹习,可動態(tài)擴(kuò)張或縮減誊抛。當(dāng)進(jìn)程調(diào)用malloc等函數(shù)分配內(nèi)存時,新分配的內(nèi)存就被動態(tài)添加到堆上(堆被擴(kuò)張)整陌;當(dāng)利用free等函數(shù)釋放內(nèi)存時拗窃,被釋放的內(nèi)存從堆中被剔除(堆被縮減)
  5. 棧 stack棧是用戶存放程序臨時創(chuàng)建的局部變量瞎领,也就是函數(shù)中定義的變量(但不包括 static 聲明的變量,static意味著在數(shù)據(jù)段中存放變量)随夸。除此以外九默,在函數(shù)被調(diào)用時,其參數(shù)也會被壓入發(fā)起調(diào)用的進(jìn)程棧中宾毒,并且待到調(diào)用結(jié)束后驼修,函數(shù)的返回值也會被存放回棧中。由于棧的先進(jìn)后出特點(diǎn)诈铛,所以棧特別方便用來保存/恢復(fù)調(diào)用現(xiàn)場乙各。從這個意義上講,我們可以把堆棿敝瘢看成一個寄存耳峦、交換臨時數(shù)據(jù)的內(nèi)存區(qū)。
  • 上述幾種內(nèi)存區(qū)域中數(shù)據(jù)段焕毫、BSS 段蹲坷、堆通常是被連續(xù)存儲在內(nèi)存中,在位置上是連續(xù)的邑飒,而代碼段和棧往往會被獨(dú)立存放循签。堆和棧兩個區(qū)域在 i386 體系結(jié)構(gòu)中棧向下擴(kuò)展、堆向上擴(kuò)展幸乒,相對而生懦底。


    image.png

內(nèi)核空間


image.png
線程

線程是操作操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。線程被包含在進(jìn)程之中罕扎,是進(jìn)程中的實(shí)際運(yùn)作單位聚唐,一個進(jìn)程內(nèi)可以包含多個線程,線程是資源調(diào)度的最小單位腔召。

image.png
線程資源和開銷

同一進(jìn)程中的多條線程共享該進(jìn)程中的全部系統(tǒng)資源杆查,如虛擬地址空間,文件描述符文件描述符和信號處理等等臀蛛。但同一進(jìn)程中的多個線程有各自的調(diào)用棧亲桦、寄存器環(huán)境、線程本地存儲等信息浊仆。

線程創(chuàng)建的開銷主要是線程堆棧的建立客峭,分配內(nèi)存的開銷。這些開銷并不大抡柿,最大的開銷發(fā)生在線程上下文切換的時候舔琅。


image.png
線程分類

還記得剛開始我們講的內(nèi)核空間和用戶空間概念嗎?線程按照實(shí)現(xiàn)位置和方式的不同洲劣,也分為用戶級線程(協(xié)程)和內(nèi)核線程备蚓,下面一起來看下這兩類線程的差異和特點(diǎn)课蔬。

用戶級線程
image.png

實(shí)現(xiàn)在用戶空間的線程稱為用戶級線程。用戶線程是完全建立在用戶空間的線程庫郊尝,用戶線程的創(chuàng)建二跋、調(diào)度、同步和銷毀全由用戶空間的庫函數(shù)完成流昏,不需要內(nèi)核的參與扎即,因此這種線程的系統(tǒng)資源消耗非常低,且非常的高效横缔。

特點(diǎn)
  • 用戶線級線程只能參與競爭該進(jìn)程的處理器資源铺遂,不能參與全局處理器資源的競爭。
  • 用戶級線程切換都在用戶空間進(jìn)行茎刚,開銷極低。
  • 用戶級線程調(diào)度器在用戶空間的線程庫實(shí)現(xiàn)撤逢,內(nèi)核的調(diào)度對象是進(jìn)程本身膛锭,內(nèi)核并不知道用戶線程的存在。
缺點(diǎn)

如果觸發(fā)了引起阻塞的系統(tǒng)調(diào)用的調(diào)用蚊荣,會立即阻塞該線程所屬的整個進(jìn)程初狰。
系統(tǒng)只看到進(jìn)程看不到用戶線程,所以只有一個處理器內(nèi)核會被分配給該進(jìn)程 互例,也就不能發(fā)揮多久 CPU 的優(yōu)勢 奢入。

內(nèi)核級線程
image.png

內(nèi)核線程建立和銷毀都是由操作系統(tǒng)負(fù)責(zé)、通過系統(tǒng)調(diào)用完成媳叨,內(nèi)核維護(hù)進(jìn)程及線程的上下文信息以及線程切換腥光。

特點(diǎn)
  • 內(nèi)核級線級能參與全局的多核處理器資源分配,充分利用多核 CPU 優(yōu)勢糊秆。
  • 每個內(nèi)核線程都可被內(nèi)核調(diào)度武福,因?yàn)榫€程的創(chuàng)建、撤銷和切換都是由內(nèi)核管理的痘番。
  • 一個內(nèi)核線程阻塞與他同屬一個進(jìn)程的線程仍然能繼續(xù)運(yùn)行捉片。
缺點(diǎn)
  • 內(nèi)核級線程調(diào)度開銷較大。調(diào)度內(nèi)核線程的代價可能和調(diào)度進(jìn)程差不多昂貴伍纫,代價要比用戶級線程大很多。
  • 線程表是存放在操作系統(tǒng)固定的表格空間或者堆棸何撸空間里,所以內(nèi)核級線程的數(shù)量是有限的说铃。
什么是協(xié)程

那什么是協(xié)程呢嘹履?攜程 Coroutines 是一種比線程更加輕量級的微線程。類比一個進(jìn)程可以擁有多個線程债热,一個線程也可以擁有多個協(xié)程砾嫉,因此協(xié)程又稱為線程的線程。


image.png
線程切換問題:

協(xié)程的調(diào)度完全由用戶控制窒篱,協(xié)程擁有自己的寄存器上下文和棧焕刮,協(xié)程調(diào)度切換時,將寄存器上下文和棧保存到其他地方墙杯,在切回來的時候配并,恢復(fù)先前保存的寄存器上下文和棧,直接操作用戶空間棧高镐,完全沒有內(nèi)核切換的開銷溉旋。

線程切換
image.png
協(xié)程切換
image.png
GO語言多線程是采用哪種線程類型(GO語言并發(fā)原理)

Golang 在語言層面實(shí)現(xiàn)了對協(xié)程的支持,Goroutine 是協(xié)程在 Go 語言中的實(shí)現(xiàn)嫉髓, 在 Go 語言中每一個并發(fā)的執(zhí)行單元叫作一個 Goroutine 观腊,Go 程序可以輕松創(chuàng)建成百上千個協(xié)程并發(fā)執(zhí)行。

!!!!通過以上分析,我們可以用JAVA語言模仿GO語言的多線程實(shí)現(xiàn)方式--->協(xié)程?

  1. 有哪些常見的語言支持協(xié)程開發(fā):
    python算行, kotlin梧油, javascript 和go
  2. JDK是否支持協(xié)程開發(fā)?
    Java 官方目前是還沒推出協(xié)程- 已加入計劃
    華為的JDK支持,但并不來源
    目前可用性比較高的有 Quasar 和 ea-async 兩個第三方庫,都是通過 byte code Instrument州邢,把編譯后同步程序class文件修改為異步的操作儡陨。

使用JAVA實(shí)現(xiàn)協(xié)程

1.引入Quasar庫

         <dependency>
            <groupId>co.paralleluniverse</groupId>
            <artifactId>quasar-core</artifactId>
            <version>0.7.9</version>
            <classifier>jdk8</classifier>
        </dependency>
代碼實(shí)現(xiàn)
  public static void main(String[] args) throws Exception {
        long startMili = System.currentTimeMillis();// 當(dāng)前時間對應(yīng)的毫秒數(shù)
        for (int i = 0; i < 100000; i++) {
            final int count = i;
            new Fiber<>((SuspendableCallable<Integer>) () -> {
                System.out.println(count);
                Fiber.sleep(1000);
                return count;
            }).start();
        }

        long endMili = System.currentTimeMillis();//結(jié)束時間
        //阻塞等待 協(xié)程執(zhí)行完畢 ----> 可采用阻塞隊列
        Thread.sleep(3000);
        System.out.println("**總耗時為:" + (endMili - startMili) + "毫秒");
    }

那么場景一使用協(xié)程處理速度是多少?

image.png
場景二:

用代碼生成1萬個文件放入文件夾對比效率: 這里我只輸出結(jié)果

  • go語言: 4.424s
    -Java語言: 4.443s
    -Java線程池: 3.208s
    -Java協(xié)程: 3.614s

結(jié)論:

換個新場景協(xié)程就沒有那么明顯的優(yōu)勢了,所以根據(jù)場景采用不通的線程開啟方式
實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn), 遇到問題建議多采用幾種方式測試
計算強(qiáng) - 建議采用多核, 任務(wù)多,多協(xié)程

使用Java線程池注意事項(慎用線程池):

  1. 線程池核心設(shè)置參數(shù)
  • 核心線程數(shù)與最大線程數(shù)可以不局限于CPU的核心數(shù)(可以根據(jù)業(yè)務(wù)場景調(diào)整)
  • 隊列容量設(shè)置理論可以無窮大,單不建議(意思是并發(fā)量多大的時候開啟新的線程)
  • 根據(jù)業(yè)務(wù)場景設(shè)置線程拒絕策略
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市量淌,隨后出現(xiàn)的幾起案子骗村,更是在濱河造成了極大的恐慌,老刑警劉巖类少,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叙身,死亡現(xiàn)場離奇詭異,居然都是意外死亡硫狞,警方通過查閱死者的電腦和手機(jī)信轿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來残吩,“玉大人财忽,你說我怎么就攤上這事∑辏” “怎么了即彪?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我隶校,道長漏益,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任深胳,我火速辦了婚禮绰疤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘舞终。我一直安慰自己轻庆,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布敛劝。 她就那樣靜靜地躺著余爆,像睡著了一般蛾方。 火紅的嫁衣襯著肌膚如雪满俗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天,我揣著相機(jī)與錄音痘儡,去河邊找鬼。 笑死渐尿,一個胖子當(dāng)著我的面吹牛矾瑰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播凉夯,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼采幌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了征绎?” 一聲冷哼從身側(cè)響起磨取,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎凫岖,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體隘截,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡婶芭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年犀农,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呵哨。...
    茶點(diǎn)故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡孟害,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出击你,到底是詐尸還是另有隱情,我是刑警寧澤丁侄,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布鸿摇,位于F島的核電站劈猿,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏糙臼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一必逆、第九天 我趴在偏房一處隱蔽的房頂上張望名眉。 院中可真熱鬧,春花似錦损拢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至邮破,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抒和,已是汗流浹背彤蔽。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留范嘱,地道東北人员魏。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓撕阎,卻偏偏與公主長得像碌补,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子镇匀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評論 2 348

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