「JAVA」線程基礎(chǔ)知識(shí)不牢固亿汞?別愁,我不僅梳理好了揪阿,還附帶了案例

Java 線程創(chuàng)建和并發(fā)基礎(chǔ)

程序在沒有流程控制的前提下疗我,代碼都是從上而下逐行依次執(zhí)行的咆畏。基于這樣的機(jī)制吴裤,如果我們使用程序來實(shí)現(xiàn)邊打游戲旧找,邊聽音樂的需求時(shí),就會(huì)很困難麦牺;因?yàn)榘凑請(qǐng)?zhí)行順序钮蛛,只能從上往下依次執(zhí)行;同一時(shí)刻剖膳,只能執(zhí)行聽音樂和打游戲的其中之一魏颓。

為了解決這樣的問題,在程序設(shè)計(jì)中引入了多線程并發(fā)吱晒。本文中的知識(shí)對(duì)windows甸饱、maclinux系統(tǒng)都適用仑濒,但展示界面和功能名稱上不太一樣叹话;相關(guān)的截圖這里以windows為例。

并行和并發(fā)

并行和并發(fā)是兩個(gè)很容易混淆的概念墩瞳,他們?cè)谧置嫔侠斫馄饋砜赡軟]有很大的差異驼壶,但要放在計(jì)算機(jī)運(yùn)行環(huán)境中來解釋,兩者是有很大區(qū)別的:

  • 并行:多個(gè)事件在同一個(gè)時(shí)間點(diǎn)同時(shí)發(fā)生矗烛,是真正的同時(shí)發(fā)生辅柴;
  • 并發(fā):多個(gè)事件在同一時(shí)間段內(nèi)在宏觀上同時(shí)發(fā)生,而在微觀上是CPU在多個(gè)事件上來回切換瞭吃,但切換的時(shí)間很快碌嘀,并不能被人眼捕獲,因此在一段人類可以觀察到的時(shí)間內(nèi)歪架,多個(gè)事件是同時(shí)發(fā)生的股冗;
并行和并發(fā)

操作系統(tǒng)的運(yùn)行環(huán)境中,并發(fā)指的就是一段時(shí)間內(nèi)宏觀上多個(gè)程序在同時(shí)運(yùn)行和蚪;在單CPU的環(huán)境中止状,微觀上每一個(gè)時(shí)刻僅有一個(gè)程序被CPU執(zhí)行(也就是僅有一個(gè)程序獲得了CPU時(shí)間片),CPU是在多個(gè)程序之間來回交替執(zhí)行攒霹,也就是給每個(gè)程序的運(yùn)行時(shí)間進(jìn)行調(diào)度怯疤,從而實(shí)現(xiàn)多個(gè)程序的并發(fā)運(yùn)行。

隨著計(jì)算機(jī)硬件的不斷發(fā)展催束,現(xiàn)如今的計(jì)算機(jī)一般都是有多個(gè)CPU的集峦,在這樣的多個(gè)CPU的環(huán)境中,原本由單個(gè)處理器運(yùn)行的這些程序就可以被分配給多個(gè)CPU來運(yùn)行,從而實(shí)現(xiàn)真程序的并行運(yùn)行塔淤,無論從宏觀上摘昌,還是微觀上,程序都是同時(shí)運(yùn)行的高蜂。這樣聪黎,程序的運(yùn)行效率就會(huì)大大提高。
PS:CPU時(shí)間片就是CPU分配給每個(gè)程序的運(yùn)行時(shí)間备恤。

在買電腦的時(shí)候稿饰,電腦廠商宣傳的“幾核處理器”,其中“核”表示的是CPU有幾個(gè)物理核心烘跺,能夠并行處理幾個(gè)程序的調(diào)用湘纵。想要知道自己電腦是幾核的,可以打開“任務(wù)管理器”來查看滤淳。

Windows 任務(wù)管理器

也可以通過計(jì)算機(jī)屬性梧喷、設(shè)備管理器來查看。 所以脖咐,單核處理器是不能并行運(yùn)行多個(gè)任務(wù)的铺敌,只能是多個(gè)任務(wù)在單核處理器中并發(fā)運(yùn)行,我們把每個(gè)任務(wù)用一個(gè)線程來表示屁擅,多個(gè)線程在單個(gè)處理器中的并發(fā)運(yùn)行我們稱之為線程調(diào)度偿凭。

從宏觀上講,多個(gè)線程是并行運(yùn)行的派歌;從微觀上講弯囊,多個(gè)線程是串行運(yùn)行的,也就是一個(gè)線程一個(gè)線程的運(yùn)行胶果;如果對(duì)這里的宏觀和微觀不太好理解的話匾嘱,可以把宏觀看作是站在人的角度看待程序運(yùn)行,把微觀看作是站在CPU的角度看待程序運(yùn)行早抠,這樣就好理解多了霎烙。

線程和進(jìn)程

進(jìn)程:進(jìn)程是指一個(gè)在內(nèi)存中運(yùn)行的應(yīng)用程序,每個(gè)進(jìn)程在內(nèi)存中都有一塊獨(dú)立的內(nèi)存空間蕊连。每個(gè)軟件都可以啟動(dòng)多個(gè)進(jìn)程悬垃。

windows 進(jìn)程

線程:線程指的是進(jìn)程中的一個(gè)控制單元,也就是進(jìn)程中的每個(gè)單元任務(wù)甘苍,一個(gè)進(jìn)程中可以有多個(gè)線程同時(shí)并發(fā)運(yùn)行尝蠕。

多進(jìn)程指的是操作系統(tǒng)中同時(shí)運(yùn)行的多個(gè)程序,多線程指的是同一個(gè)進(jìn)程中同時(shí)運(yùn)行的多個(gè)任務(wù)载庭。操作系統(tǒng)中運(yùn)行的每個(gè)任務(wù)就是一個(gè)進(jìn)程看彼,進(jìn)程中的每個(gè)任務(wù)就是一個(gè)線程扇谣;操作系統(tǒng)就是一個(gè)多任務(wù)系統(tǒng),它可以有多個(gè)進(jìn)程闲昭,每個(gè)進(jìn)程又可以有多個(gè)線程。

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

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

  1. 每個(gè)進(jìn)程都有獨(dú)立的內(nèi)存空間靡挥,也就是進(jìn)程中的數(shù)據(jù)存儲(chǔ)空間(堆序矩、棧空間)是獨(dú)立的跋破,且每個(gè)進(jìn)程都至少有一個(gè)線程簸淀;

  2. 對(duì)于線程來說:堆內(nèi)存空間是共享的,棧內(nèi)存空間是獨(dú)立的毒返;線程消耗的資源比進(jìn)程要小得多租幕,且線程之間是可以相互通信的;

  3. 線程是進(jìn)程的基本組成單元拧簸,故也把線程稱作進(jìn)程元劲绪,或者輕型進(jìn)程;

  4. 線程的執(zhí)行是通過CPU調(diào)度器來決定的盆赤,程序員無法控制贾富;

線程調(diào)度: 計(jì)算機(jī)單個(gè)CPU在任意時(shí)刻只能執(zhí)行一條計(jì)算機(jī)指令,每個(gè)進(jìn)程只有獲得CPU使用權(quán)才能執(zhí)行相關(guān)指令牺六;

多線程并發(fā)颤枪,其實(shí)就是運(yùn)行中各個(gè)進(jìn)程輪流獲取CPU的使用權(quán)來分別執(zhí)行各自的任務(wù);在多進(jìn)程的環(huán)境中淑际,會(huì)有多個(gè)線程處于等待獲取CPU使用權(quán)的狀態(tài)中畏纲,為這些等待中的線程分配CPU使用權(quán)的操作就成為線程調(diào)度。線程調(diào)度分為搶占式調(diào)度和分時(shí)調(diào)度春缕。

  • 搶占式調(diào)度:多個(gè)線程在瞬間搶占CPU資源盗胀,誰搶到誰就運(yùn)行,有更多的隨機(jī)性淡溯;

  • 分時(shí)調(diào)度:為等待中的多個(gè)線程平均的分配CPU時(shí)間片读整;

Java的多線程中線程調(diào)度就是使用搶占式調(diào)度的。

多線程

多線程和單線程咱娶,就好比多行道和單行道米间,多行道可以有多輛車同時(shí)行駛通過,而單行道只能是多輛車按順序依次行駛通過膘侮;多線程同時(shí)有多個(gè)線程并發(fā)運(yùn)行屈糊,單線程只有單個(gè)線程對(duì)多個(gè)任務(wù)按順序依次執(zhí)行。

如果以下載文件為例:單線程就是只有一個(gè)文件下載的通道琼了,多線程則是同時(shí)有多個(gè)下載通道在下載文件逻锐。當(dāng)服務(wù)器提供下載服務(wù)時(shí)夫晌,下載程序是共享服務(wù)器帶寬的,在優(yōu)先級(jí)相同的情況下昧诱,服務(wù)器會(huì)對(duì)下載中的所有線程平均分配帶寬:

多線程文件下載

寬帶帶寬是以位(bit)來計(jì)算的晓淀,而下載速度是以字節(jié)(byte)計(jì)算的,1 byte = 8 bit盏档,故1024KB/s代表的是上網(wǎng)寬帶為1M1024千位)凶掰,而下載速度需要用1024KB/s除去8,得出`128KB/s蜈亩。

多線程是為了同步完成多項(xiàng)任務(wù)懦窘,是為了提高系統(tǒng)整體的效率,而不能提高程序代碼自身的運(yùn)行效率稚配。

多線程的優(yōu)勢:多線程作為一種多任務(wù)畅涂、高并發(fā)的運(yùn)行機(jī)制,有其獨(dú)到的優(yōu)勢所在:

  1. 進(jìn)程之間不能共享內(nèi)存空間道川,但是線程之間是可以的(通過堆內(nèi)存)午衰;

  2. 創(chuàng)建進(jìn)程時(shí),操作系統(tǒng)需要為其重新分配系統(tǒng)資源愤惰;而創(chuàng)建線程耗費(fèi)的資源會(huì)小很多苇经,在實(shí)現(xiàn)多任務(wù)并發(fā)時(shí),相比較于多進(jìn)程宦言,多線程的效率會(huì)高很多扇单;

  3. Java 語言內(nèi)置了對(duì)多線程的支持,而不僅僅是簡單的調(diào)用底層操作系統(tǒng)的調(diào)度奠旺;Java 對(duì)多線程的支持也很友好蜘澜,能大大簡化開發(fā)成本;

創(chuàng)建進(jìn)程

Java中創(chuàng)建進(jìn)程可通過兩種方式來實(shí)現(xiàn):
1. 通過java.lang.Runtime來實(shí)現(xiàn)响疚,示例代碼如下:

  public  static  void  main(String  []args)  throws  IOException  {  
        // 方式一:通過通過java.lang.Runtime來實(shí)現(xiàn)打開 cmd  
        Runtime runtime =  Runtime.getRuntime(); 
        runtime.exec("cmd");  
}  

2. 通過java.lang.ProcessBuilder 來實(shí)現(xiàn)鄙信,示例代碼如下:

public  static  void  main(String  []args)  throws  IOException  {  
        // 方式二:通過通過java.lang.ProcessBuilder來實(shí)現(xiàn)打開 cmd
        ProcessBuilder pb =  new  ProcessBuilder("cmd"); 
        pb.start();  
}

創(chuàng)建線程

一、通過繼承Thread類創(chuàng)建線程忿晕;需要注意的是:只有Thread的子類才是線程類装诡;

  1. 新創(chuàng)建一個(gè)類繼承于java.lang.Thread

  2. 在新建的Thread子類中重寫Thread類中的run方法践盼,在run方法中編寫線程邏輯鸦采;

  3. 創(chuàng)建線程對(duì)象,執(zhí)行線程邏輯咕幻;

  public  class  ExtendsThreadDemo  {  
          public  static  void  main(String  []args)  {  
                 for  (int i =  0; i <  50; i++)  {  
                         System.out.println("主線程"  + i);  
                         if  (i ==  13)  {  
                              NewThread newThread =  new  NewThread(); 
                              newThread.start();  
                          }  
                  }  
            }  
}  

// 新線程類  
class  NewThread  extends  Thread  {  
        @Override  
         public  void  run()  {  
                for  (int i =  0; i <  50; i++)  {  
                      System.out.println("新線程"  + i);  
                }  
          }  
}  

二渔伯、通過實(shí)現(xiàn)Runnable接口創(chuàng)建線程;需要注意肄程,這里的Runnable實(shí)現(xiàn)類并不是線程類锣吼,所以啟動(dòng)方式和Thread子類會(huì)有所不同选浑;

  1. 新創(chuàng)建一個(gè)類實(shí)現(xiàn)java.lang.Runnable
  2. 在新建的實(shí)現(xiàn)類中重寫Runnable類中的run方法玄叠,在run方法中編寫線程邏輯古徒;
  3. 創(chuàng)建Thread對(duì)象,傳入Runnable實(shí)現(xiàn)類對(duì)象读恃,執(zhí)行線程邏輯描函;

示例代碼如下:

public class ImplementsRunnableDemo {
    public static void main(String []args) {
        for (int i = 0; i < 50; i++) {
            System.out.println("主線程" + i);
            if (i == 13) {
                Runnable runnable = new NewRunnableImpl();
                Thread thread = new Thread(runnable);
                thread.start();
            }
        }
 
    }
}

// 新線程類
class NewRunnableImpl implements Runnable {
 
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("新線程" + i);
        }
    }
}

三、使用匿名內(nèi)部類創(chuàng)建線程狐粱,使用接口的匿名內(nèi)部類來創(chuàng)建線程,示例代碼如下:

// 使用接口的匿名內(nèi)部類
public class AnonymousInnerClassDemo {
 
    public static void main(String []args) {
        for (int i = 0; i < 50; i++) {
          System.out.println("主線程" + i);
          if (i == 13) {
            new Thread(new Runnable() {
              @Override
              public void run() {
                for (int j = 0; j < 50; j++) {
                  System.out.println("新線程" + j);
                }
              }
            }).start();
          }
        }
    }
}

當(dāng)然了胆数,也可以使用Thread類的匿名內(nèi)部類創(chuàng)建線程肌蜻,不過這樣的方式很少使用;示例代碼如下:

// 使用Thread類的匿名內(nèi)部類 
public class AnonymousInnerClassDemo {
 
    public static void main(String []args) {
        for (int i = 0; i < 50; i++) {
          System.out.println("主線程" + i);
          if (i == 13) {
            new Thread() {
              @Override
              public void run() {
                for (int j = 0; j < 50; j++) {
                  System.out.println("新線程" + j);
                }
              }
            }.start();
          }
        }
    }
}

多線程案例

案例需求:六一兒童節(jié)必尼,設(shè)置了搶氣球比賽節(jié)目蒋搜,共有50個(gè)氣球,三個(gè)小朋友小紅判莉、小強(qiáng)豆挽、小明來搶;請(qǐng)使用多線程技術(shù)來實(shí)現(xiàn)上述比賽過程券盅。

氣球比賽

一帮哈、使用繼承Thread類的方式來實(shí)現(xiàn)上述案例;示例代碼如下:

public class ExtendsDemo {
    public static void main(String []args) {
 
        new Student("小紅").start();
        new Student("小強(qiáng)").start();
        new Student("小明").start();
 
    }
}

class Student extends Thread {
 
    private int num = 50;
    private String name;
    public Student(String name) {
        super(name);
        this.name = name;
    }
 
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            if (num > 0) {
                System.out.println(this.name + "搶到了" + num + "號(hào)氣球");
                num--;
            }
        }
    }
}

通過查看輸出結(jié)果锰镀,發(fā)現(xiàn)一個(gè)問題:每個(gè)小朋友都搶到了50個(gè)氣球娘侍,這和原本只有50個(gè)氣球相矛盾了;不過別急泳炉,我們可以使用第二種方式:使用實(shí)現(xiàn)接口的方式來實(shí)現(xiàn)上述案例 來解決憾筏。

二、使用實(shí)現(xiàn)接口的方式來實(shí)現(xiàn)上述案例花鹅;示例代碼如下:

public class ImplementsDemo {
    public static void main(String []args) {
        Balloon balloon = new Balloon();
        new Thread(balloon, "小紅").start();
        new Thread(balloon, "小強(qiáng)").start();
        new Thread(balloon, "小明").start();
    }
}

// 氣球
class Balloon implements Runnable {
 
    private int num = 50;
 
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
        if (num > 0) {
          System.out.println(Thread.currentThread().getName() + "搶到了" 
                     + num + "號(hào)氣球");
          num--;
        }
        }
    }
 
}

在該案例中我們是用了Thread.currentThread()方法氧腰,該方法的作用是返回當(dāng)前正在執(zhí)行的線程對(duì)象的引用,所以當(dāng)前正在執(zhí)行的線程對(duì)象的名稱就可以這樣來獲扰偎唷:String name = Thread.currentThread().getName();古拴。

通過查看該案例的打印結(jié)果,不難發(fā)現(xiàn):三個(gè)小朋友一共搶到了50個(gè)氣球之景,符合了需求中規(guī)氣球總共有50個(gè)的要求斤富。我們?cè)賮矸治鲋骱瘮?shù)中的代碼,發(fā)現(xiàn)是因?yàn)?個(gè)線程共享了一個(gè)Balloon對(duì)象锻狗,該對(duì)象中的氣球數(shù)量就在50個(gè)满力。

共享Balloon對(duì)象的堆內(nèi)存模型

按照這樣的思路焕参,上述使用繼承Thread類的方式中出現(xiàn)的問題就可以解決了。接下來就對(duì)上述兩種實(shí)現(xiàn)多線程的方式進(jìn)行分析和總結(jié):

使用繼承Thread類的方式:

  1. 使用繼承方式來實(shí)現(xiàn)多線程在操作上會(huì)更加簡便油额;比如:可以通過super.getName()來直接獲取當(dāng)前線程對(duì)象的名稱叠纷;
  2. 由于Java 是單繼承的,所以如果繼承了Thread潦嘶,該類就不能再有其他的父類了涩嚣;
  3. 對(duì)于搶氣球案例需求來說,并不能很好的解決問題掂僵;

使用實(shí)現(xiàn)接口的方式:

  1. 相較于繼承方式航厚,實(shí)現(xiàn)方式和線程操作會(huì)稍加復(fù)雜;比如:獲取當(dāng)前線程名稱需要通過Thread.currentThread().getName();來獲让膛睢幔睬;
  2. 由于是使用實(shí)現(xiàn)的方式,Java 是支持多實(shí)現(xiàn)的芹扭,所以除了Runnable接口之外麻顶,還可以實(shí)現(xiàn)其他的接口,繼承另外的類舱卡;
  3. 能夠很好的實(shí)現(xiàn)案例需求:多個(gè)線程共享一個(gè)資源辅肾;

完結(jié),老夫雖不正經(jīng)轮锥,但老夫一身的才華矫钓!關(guān)注我,獲取更多編程基礎(chǔ)知識(shí)舍杜。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末份汗,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蝴簇,更是在濱河造成了極大的恐慌杯活,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件熬词,死亡現(xiàn)場離奇詭異旁钧,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)互拾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門歪今,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人颜矿,你說我怎么就攤上這事寄猩。” “怎么了骑疆?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵田篇,是天一觀的道長替废。 經(jīng)常有香客問我,道長泊柬,這世上最難降的妖魔是什么椎镣? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮兽赁,結(jié)果婚禮上状答,老公的妹妹穿的比我還像新娘。我一直安慰自己刀崖,他們只是感情好惊科,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著亮钦,像睡著了一般译断。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上或悲,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音堪唐,去河邊找鬼巡语。 笑死,一個(gè)胖子當(dāng)著我的面吹牛淮菠,可吹牛的內(nèi)容都是我干的男公。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼合陵,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼枢赔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拥知,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤踏拜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后低剔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體速梗,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年襟齿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了姻锁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡猜欺,死狀恐怖位隶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情开皿,我是刑警寧澤涧黄,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布篮昧,位于F島的核電站,受9級(jí)特大地震影響弓熏,放射性物質(zhì)發(fā)生泄漏恋谭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一挽鞠、第九天 我趴在偏房一處隱蔽的房頂上張望疚颊。 院中可真熱鬧,春花似錦信认、人聲如沸材义。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽其掂。三九已至,卻和暖如春潦蝇,著一層夾襖步出監(jiān)牢的瞬間款熬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來泰國打工攘乒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贤牛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓则酝,卻偏偏與公主長得像殉簸,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子沽讹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359