<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          拜托!別再問我多線程的這些問題了

          共 2515字,需瀏覽 6分鐘

           ·

          2020-09-12 11:20






          很多同學(xué)面對(duì)多線程的問題都很頭大,因?yàn)樽约鹤鲰?xiàng)目很難用到,但是但凡高薪的職位面試都會(huì)問到。。畢竟現(xiàn)在大廠里用的都是多線程高并發(fā),所以這塊內(nèi)容不吃透肯定是不行的。

          今天這篇文章,作為多線程的基礎(chǔ)篇,先來談?wù)勔韵聠栴}:

          1. 為什么要用多線程?
          2. 程序 vs 進(jìn)程 vs 線程
          3. 創(chuàng)建線程的 4 種方式?

          為什么要用多線程

          任何一項(xiàng)技術(shù)的出現(xiàn)都是為了解決現(xiàn)有問題。

          之前的互聯(lián)網(wǎng)大多是單機(jī)服務(wù),體量??;而現(xiàn)在的更多是集群服務(wù),同一時(shí)刻有多個(gè)用戶同時(shí)訪問服務(wù)器,那么會(huì)有很多線程并發(fā)訪問。

          比如在電商系統(tǒng)里,同一時(shí)刻比如整點(diǎn)搶購(gòu)時(shí),大量用戶同時(shí)訪問服務(wù)器,所以現(xiàn)在公司里開發(fā)的基本都是多線程的。

          使用多線程確實(shí)提高了運(yùn)行的效率,但與此同時(shí),我們也需要特別注意數(shù)據(jù)的增刪改情況,這就是線程安全問題,比如之前說過的 HashMap vs HashTable,Vector vs ArrayList

          要保證線程安全也有很多方式,比如說加鎖,但又可能會(huì)出現(xiàn)其他問題比如死鎖,所以多線程相關(guān)問題會(huì)比較麻煩。

          因此,我們需要理解多線程的原理和它可能會(huì)產(chǎn)生的問題以及如何解決問題,才能拿下高薪職位。

          進(jìn)程 vs 線程

          程序 program

          說到進(jìn)程,就不得不先說說程序。

          程序,說白了就是代碼,或者說是一系列指令的集合。比如「微信.exe」這就是一個(gè)程序,這個(gè)文件最終是要拿到 CPU 里面去執(zhí)行的。

          進(jìn)程 process

          當(dāng)程序運(yùn)行起來,它就是一個(gè)進(jìn)程

          所以程序是“死”的,進(jìn)程是“活”的。

          比如在任務(wù)管理器里的就是一個(gè)個(gè)進(jìn)程,就是“動(dòng)起來”的應(yīng)用程序。

          Q:這些進(jìn)程是并行執(zhí)行的嗎?

          單核 CPU 一個(gè)時(shí)間片里只能執(zhí)行一個(gè)進(jìn)程。但是因?yàn)樗袚Q速度很快,所以我們感受不到,就造成了一種多進(jìn)程的假象。(多核 CPU 那真的就是并行執(zhí)行的了。)

          Q:那如果這個(gè)進(jìn)程沒執(zhí)行完呢?

          當(dāng)進(jìn)程 A 執(zhí)行完一個(gè)時(shí)間片,但是還沒執(zhí)行完時(shí),為了方便下次接著執(zhí)行,要保存剛剛執(zhí)行完的這些數(shù)據(jù)信息,叫做「保存現(xiàn)場(chǎng)」。

          然后等下次再搶到了資源執(zhí)行的時(shí)候,先「恢復(fù)現(xiàn)場(chǎng)」,再開始繼續(xù)執(zhí)行。

          這樣循環(huán)往復(fù)。。

          這樣反復(fù)的保存啊、恢復(fù)啊,都是額外的開銷,也會(huì)讓程序執(zhí)行變慢。

          Q:有沒有更高效的方式呢?

          如果兩個(gè)線程歸屬同一個(gè)進(jìn)程,就不需要保存、恢復(fù)現(xiàn)場(chǎng)了。

          這就是 NIO 模型的思路,也是 NIO 模型比 BIO 模型效率高很多的原因,我們之后再講。

          線程 thread

          線程,是一個(gè)進(jìn)程里的具體的執(zhí)行路徑,就是真正干活的。

          在一個(gè)進(jìn)程里,一個(gè)時(shí)間片也只能有一個(gè)線程在執(zhí)行,但因?yàn)闀r(shí)間片的切換速度非??欤钥雌饋砭秃孟袷峭瑫r(shí)進(jìn)行的。

          一個(gè)進(jìn)程里至少有一個(gè)線程。比如主線程,就是我們平時(shí)寫的 main() 函數(shù),是用戶線程;還有 gc 線程是 JVM 生產(chǎn)的,負(fù)責(zé)垃圾回收,是守護(hù)線程。

          每個(gè)線程有自己的 stack,記錄該線程里面的方法相互調(diào)用的關(guān)系;

          但是一個(gè)進(jìn)程里的所有線程是共用堆 heap 的。

          那么不同的進(jìn)程之間是不可以互相訪問內(nèi)存的,每個(gè)進(jìn)程有自己的內(nèi)存空間 memeory space,也就是虛擬內(nèi)存 virtual memory。

          通過這個(gè)虛擬內(nèi)存,每一個(gè)進(jìn)程都感覺自己擁有了整個(gè)內(nèi)存空間。

          虛擬內(nèi)存的機(jī)制,就是屏蔽了物理內(nèi)存的限制。

          Q:那如果物理內(nèi)存被用完了呢?

          用硬盤,比如 windows 系統(tǒng)的分頁(yè)文件,就是把一部分虛擬內(nèi)存放到了硬盤上。

          相應(yīng)的,此時(shí)程序運(yùn)行會(huì)很慢,因?yàn)橛脖P的讀寫速度比內(nèi)存慢很多,是我們可以感受到的慢,這就是為什么開多了程序電腦就會(huì)變卡的原因。

          Q:那這個(gè)虛擬內(nèi)存是有多大呢?

          對(duì)于 64 位操作系統(tǒng)來說,每個(gè)程序可以用 64 個(gè)二進(jìn)制位,也就是 2^64 這么大的空間!

          總結(jié)

          總結(jié)一下,在一個(gè)時(shí)間片里,一個(gè) CPU 只能執(zhí)行一個(gè)進(jìn)程。

          CPU 給某個(gè)進(jìn)程分配資源后,這個(gè)進(jìn)程開始運(yùn)行;進(jìn)程里的線程去搶占資源,一個(gè)時(shí)間片就只有一個(gè)線程能執(zhí)行,誰(shuí)先搶到就是誰(shuí)的。

          多進(jìn)程 vs 多線程

          每個(gè)進(jìn)程是獨(dú)立的,進(jìn)程 A 出問題不會(huì)影響到進(jìn)程 B;

          雖然線程也是獨(dú)立運(yùn)行的,但是一個(gè)進(jìn)程里的線程是共用同一個(gè)堆,如果某個(gè)線程 out of memory,那么這個(gè)進(jìn)程里所有的線程都完了。

          所以多進(jìn)程能夠提高系統(tǒng)的容錯(cuò)性 fault tolerance ,而多線程最大的好處就是線程間的通信非常方便。

          進(jìn)程之間的通信需要借助額外的機(jī)制,比如進(jìn)程間通訊 interprocess communication - IPC,或者網(wǎng)絡(luò)傳遞等等。

          如何創(chuàng)建線程

          上面說了一堆概念,接下來我們看具體實(shí)現(xiàn)。

          Java 中是通過 java.lang.Thread 這個(gè)類來實(shí)現(xiàn)多線程的功能的,那我們先來看看這個(gè)類。

          從文檔中我們可以看到,Thread 類是直接繼承 Object 的,同時(shí)它也是實(shí)現(xiàn)了 Runnable 接口。

          官方文檔里也寫明了 2 種創(chuàng)建線程的方式:

          一種方式是從 Thread 類繼承,并重寫 run(),run() 方法里寫的是這個(gè)線程要執(zhí)行的代碼;

          啟動(dòng)時(shí)通過 new 這個(gè) class 的一個(gè)實(shí)例,調(diào)用 start() 方法啟動(dòng)線程。

          二是實(shí)現(xiàn) Runnable 接口,并實(shí)現(xiàn) run(),run() 方法里同樣也寫的是這個(gè)線程要執(zhí)行的代碼;

          稍有不同的是啟動(dòng)線程,需要 new 一個(gè)線程,并把剛剛創(chuàng)建的這個(gè)實(shí)現(xiàn)了 Runnable 接口的類的實(shí)例傳進(jìn)去,再調(diào)用 start(),這其實(shí)是代理模式。

          如果面試官問你,還有沒有其他的,那還可以說:

          1. 實(shí)現(xiàn) Callable 接口;

          2. 通過線程池來啟動(dòng)一個(gè)線程。

          但其實(shí),用線程池來啟動(dòng)線程時(shí)也是用的前兩種方式之一創(chuàng)建的。

          這兩種方式在這里就不細(xì)說啦,我們具體來看前兩種方式。

          繼承 Thread 類

          public?class?MyThread?extends?Thread?{
          ????@Override
          ????public?void?run()?{
          ????????for?(int?i?=?0;?i?100;?i++)?{
          ????????????System.out.println("小齊666:"?+?i);
          ????????}
          ????}
          ????public?static?void?main(String[]?args)?{
          ????????MyThread?myThread?=?new?MyThread();
          ????????myThread.start();
          ????????for?(int?i?=?0;?i?100;?i++)?{
          ????????????System.out.println("主線程"?+?i?+?":齊姐666");
          ????????}
          ????}
          }

          在這里,

          • main 函數(shù)是主線程,是程序的入口,執(zhí)行整個(gè)程序;

          • 程序開始執(zhí)行后先啟動(dòng)了一個(gè)新的線程 myThread,在這個(gè)線程里輸出“小齊”;

          • 主線程并行執(zhí)行,并輸出“主線程i:齊姐”。

          來看下結(jié)果,就是兩個(gè)線程交替夸我嘛~

          Q:為啥和我運(yùn)行的結(jié)果不一樣?

          多線程中,每次運(yùn)行的結(jié)果可能都會(huì)不一樣,因?yàn)槲覀儫o法人為控制哪條線程在什么時(shí)刻先搶到資源。

          當(dāng)然了,我們可以給線程加上優(yōu)先級(jí) priority,但高優(yōu)先級(jí)也無法保證這條線程一定能先被執(zhí)行,只能說有更大的概率搶到資源先執(zhí)行。

          實(shí)現(xiàn) Runnable 接口

          這種方式用的更多。

          public?class?MyRunnable?implements?Runnable?{
          ????@Override
          ????public?void?run()?{
          ????????for(int?i?=?0;?i?100;?i++)?{
          ????????????System.out.println("小齊666:"?+?i);
          ????????}
          ????}

          ????public?static?void?main(String[]?args)?{
          ????????new?Thread(new?MyRunnable()).start();

          ????????for(int?i?=?0;?i?100;?i++)?{
          ????????????System.out.println("主線程"?+?i?+?":齊姐666");
          ????????}
          ????}
          }

          結(jié)果也差不多:

          像前文所說,這里線程啟動(dòng)的方式和剛才的稍有不同,因?yàn)樾陆ǖ牡倪@個(gè)類只是實(shí)現(xiàn)了 Runnable 接口,所以還需要一個(gè)線程來“代理”執(zhí)行它,所以需要把我們新建的這個(gè)類的實(shí)例傳入到一個(gè)線程里,這里其實(shí)是代理模式。這個(gè)設(shè)計(jì)模式之后再細(xì)講。

          小結(jié)

          那這兩種方式哪種好呢?

          使用 Runnable 接口更好,主要原因是 Java 單繼承。

          另外需要注意的是,在啟動(dòng)線程的的時(shí)候用的是 start(),而不是 run()。

          調(diào)用 run() 僅僅是調(diào)用了這個(gè)方法,是普通的方法調(diào)用;而 start() 才是啟動(dòng)線程,然后由 JVM 去調(diào)用該線程的 run()

          好了,以上就是多線程第一篇的所有內(nèi)容了,這里主要是幫助大家復(fù)習(xí)一下基礎(chǔ)概念,以及沒有接觸過多線程的小伙伴可以入門。想看更多關(guān)于多線程的文章的話,記得給我點(diǎn)贊留言哦~

          你們的支持和認(rèn)可,就是我創(chuàng)作的最大動(dòng)力,我們下篇文章見!


          ? ? ? ?
          ???
          Java 線程池中的線程復(fù)用是如何實(shí)現(xiàn)的?
          為什么阿里巴巴Java開發(fā)手冊(cè)中強(qiáng)制要求線程池不允許使用Executors創(chuàng)建?
          如何優(yōu)雅地中止線程?

          覺得不錯(cuò),點(diǎn)個(gè)在看~

          瀏覽 40
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩精品a∨中文在线播放 | 天干天干天夜夜爽 | 中文字幕第256页 | 精品毛片aaa级 | 日韩欧美三级在线观看 |