Java多線程(一)多線程基礎(chǔ)

前言

本文主要講解java多線程的基礎(chǔ),以及一些常用方法外邓。關(guān)于線程同步益咬、ExecutorService框架我會放到后續(xù)的文章進(jìn)行講解侈玄。


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

進(jìn)程

進(jìn)程簡單的來說就是在內(nèi)存中運(yùn)行的應(yīng)用程序婉刀,一個(gè)進(jìn)程可以啟動多個(gè)線程。
比如在windows中一個(gè)運(yùn)行EXE文件就是一個(gè)進(jìn)程拗馒。

線程

同一個(gè)線程中的進(jìn)程共用相同的地址空間路星,同時(shí)共享進(jìn)程所擁有的內(nèi)存和其他資源。


線程Demo-繼承Thread類

首先我們我們繼承java.lang.Thread類來創(chuàng)建線程诱桂。

package top.crosssoverjie.study.Thread;

public class TestThread {
    public static void main(String[] args) {
        System.out.println("主線程ID是:" + Thread.currentThread().getId());
        MyThread my = new MyThread("線程1");
        my.start() ;
        
        MyThread my2 = new MyThread("線程2") ;
        /**
         * 這里直接調(diào)用my2的run()方法洋丐。
         */
        my2.run() ;
    }

}

class MyThread extends Thread {
    private String name;

    public MyThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println("名字:" + name + "的線程ID是="
                + Thread.currentThread().getId());
    }

}

輸出結(jié)果:

主線程ID是:1
名字:線程2的線程ID是=1
名字:線程1的線程ID是=9

由輸出結(jié)果我們可以得出以下結(jié)論:

  • my和my2的線程ID不相同,my2和主線程ID相同挥等。說明直接調(diào)用run()方法不會創(chuàng)建新的線程友绝,而是在主線程中直接調(diào)用的run()方法,和普通的方法調(diào)用沒有區(qū)別。
  • 雖然my的start()方法是在my2的run()方法之前調(diào)用肝劲,但是卻是后輸出內(nèi)容迁客,說明新建的線程并不會影響主線程的執(zhí)行。

線程Demo-實(shí)現(xiàn)Runnable接口

除了繼承java.lang.Thread類之外辞槐,我們還可以實(shí)現(xiàn)java.lang.Runnable接口來創(chuàng)建線程掷漱。

package top.crosssoverjie.study.Thread;

public class TestRunnable {
    public static void main(String[] args) {
        System.out.println("主線程的線程ID是"+Thread.currentThread().getId());
        MyThread2 my = new MyThread2("線程1") ;
        Thread t = new Thread(my) ;
        t.start() ;
        
        MyThread2 my2 = new MyThread2("線程2") ;
        Thread t2 = new Thread(my2) ;
        /**
         * 方法調(diào)用,并不會創(chuàng)建線程榄檬,依然是主線程
         */
        t2.run() ;
    }
}

class MyThread2 implements Runnable{
    private String name ;
    public MyThread2(String name){
        this.name = name ;
    }

    @Override
    public void run() {
        System.out.println("線程"+name+"的線程ID是"+Thread.currentThread().getId());
    }
    
    
}

輸出結(jié)果:

主線程的線程ID是1
線程線程2的線程ID是1
線程線程1的線程ID是9

notes:

  • 實(shí)現(xiàn)Runnable的方式需要將實(shí)現(xiàn)Runnable接口的類作為參數(shù)傳遞給Thread卜范,然后通過Thread類調(diào)用Start()方法來創(chuàng)建線程。
  • 這兩種方式都可以來創(chuàng)建線程鹿榜,至于選擇哪一種要看自己的需求海雪。直接繼承Thread類的話代碼要簡潔一些,但是由于java只支持單繼承舱殿,所以如果要繼承其他類的同時(shí)需要實(shí)現(xiàn)線程那就只能實(shí)現(xiàn)Runnable接口了奥裸,這里更推薦實(shí)現(xiàn)Runnable接口。

實(shí)際上如果我們查看Thread類的源碼我們會發(fā)現(xiàn)Thread是實(shí)現(xiàn)了Runnable接口的:


Thread源碼

線程中常用的方法

序號 方法 介紹
1 public void start() 使該線程執(zhí)行沪袭,java虛擬機(jī)會調(diào)用該線程的run()方法湾宙。
2 public final void setName(String name) 修改線程名稱。
3 public final void setPriority(int privority) 修改線程的優(yōu)先級冈绊。
4 public final void setDaemon(false on) 將該線程標(biāo)記為守護(hù)線程或用戶線程创倔,當(dāng)正在運(yùn)行線程都是守護(hù)線程時(shí),java虛擬機(jī)退出焚碌,該方法必須在啟動線程前調(diào)用。
5 public final void join(long mills) 等待該線程的終止時(shí)間最長為mills毫秒霸妹。
6 public void interrupt() 中斷線程十电。
7 public static boolean isAlive() 測試線程是否處于活動狀態(tài)。如果該線程已經(jīng)啟動尚未終止,則為活動狀態(tài)鹃骂。
8 public static void yield() 暫停當(dāng)前線程執(zhí)行的對象台盯,并執(zhí)行其他線程。
9 public static void sleep(long mills) 在指定毫秒數(shù)內(nèi)畏线,讓當(dāng)前執(zhí)行的線程休眠(暫停)静盅。
10 public static Thread currentThread() 返回當(dāng)前線程的引用。

方法詳解- public static void sleep(long mills)

package top.crosssoverjie.study.Thread;

public class TestSleep {

    private int i = 10 ;
    private Object ob = new Object() ;
    
    public static void main(String[] args) {
        TestSleep t = new TestSleep() ;
        MyThread3 thread1 = t.new MyThread3() ;
        MyThread3 thread2 = t.new MyThread3() ;
        thread1.start() ;
        thread2.start() ;
    }
    
    class MyThread3 extends Thread{
        @Override
        public void run() {
            synchronized (ob) {
                i++ ;
                System.out.println("i的值:"+i);
                System.out.println("線程:"+Thread.currentThread().getName()+"進(jìn)入休眠狀態(tài)");
                try {
                    Thread.currentThread().sleep(1000) ;
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("線程:"+Thread.currentThread().getName()+"休眠結(jié)束");
                i++;
                System.out.println("i的值>:"+i);
            }
        }
    }
    
}

輸出結(jié)果:

i的值:11
線程:Thread-0進(jìn)入休眠狀態(tài)
線程:Thread-0休眠結(jié)束
i的值>:12
i的值:13
線程:Thread-1進(jìn)入休眠狀態(tài)
線程:Thread-1休眠結(jié)束
i的值>:14

由輸出結(jié)果我們可以得出:

  • 當(dāng)Thread0進(jìn)入休眠狀態(tài)時(shí)寝殴,Thread1并沒有繼續(xù)執(zhí)行蒿叠,而是等待Thread0休眠結(jié)束釋放了對象鎖,Thread1才繼續(xù)執(zhí)行蚣常。
    當(dāng)調(diào)用sleep()方法時(shí)市咽,必須捕獲異常或者向上層拋出異常抵蚊。當(dāng)線程休眠時(shí)間滿時(shí)施绎,并不一定會馬上執(zhí)行,因?yàn)榇藭r(shí)有可能CPU正在執(zhí)行其他的任務(wù)贞绳,所以調(diào)用了sleep()方法相當(dāng)于線程進(jìn)入了阻塞狀態(tài)谷醉。

方法詳解- public static void yield()

package top.crosssoverjie.study.Thread;

public class Testyield {
    public static void main(String[] args) {
        MyThread4 my = new MyThread4() ;
        my.start() ;
    }
}
class MyThread4 extends Thread{
    @Override
    public void run() {
        long open = System.currentTimeMillis();
        int count= 0 ;
        for(int i=0 ;i<1000000;i++){
            count= count+(i+1);
//            Thread.yield() ;
        }
        long end = System.currentTimeMillis();
        System.out.println("用時(shí):"+(end-open)+"毫秒");
    }
}

輸出結(jié)果:
用時(shí):1毫秒
如果將 Thread.yield()注釋取消掉,輸出結(jié)果:
用時(shí):116毫秒

  • 調(diào)用yield()方法是為了讓當(dāng)前線程交出CPU權(quán)限冈闭,讓CPU去執(zhí)行其他線程俱尼。它和sleep()方法類似同樣是不會釋放鎖。但是yield()不能控制具體的交出CUP的時(shí)間拒秘。并且它只能讓相同優(yōu)先級的線程獲得CPU執(zhí)行時(shí)間的機(jī)會号显。
  • 調(diào)用yield()方法不會讓線程進(jìn)入阻塞狀態(tài),而是進(jìn)入就緒狀態(tài)躺酒,它只需要等待重新獲取CPU的時(shí)間押蚤,這一點(diǎn)和sleep()方法是不一樣的。

方法詳解- public final void join()

在很多情況下我們需要在子線程中執(zhí)行大量的耗時(shí)任務(wù)羹应,但是我們主線程又必須得等待子線程執(zhí)行完畢之后才能結(jié)束揽碘,這就需要用到 join()方法了。join()方法的作用是等待線程對象銷毀园匹,如果子線程執(zhí)行了這個(gè)方法雳刺,那么主線程就要等待子線程執(zhí)行完畢之后才會銷毀,請看下面這個(gè)例子:

package top.crosssoverjie.study.Thread;

public class Testjoin {
    public static void main(String[] args) throws InterruptedException {
        new MyThread5("t1").start() ;
        for (int i = 0; i < 10; i++) {
            if(i == 5){
                MyThread5 my =new MyThread5("t2") ;
                my.start() ;
                my.join() ;
            }
            System.out.println("main當(dāng)前線程:"+Thread.currentThread().getName()+" "+i);
        }
    }
}
class MyThread5 extends Thread{
    
    public MyThread5(String name){
        super(name) ;
    }
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("當(dāng)前線程:"+Thread.currentThread().getName()+" "+i);
        }
    }
}

輸出結(jié)果:

main當(dāng)前線程:main 0
當(dāng)前線程:t1 0
當(dāng)前線程:t1 1
main當(dāng)前線程:main 1
當(dāng)前線程:t1 2
main當(dāng)前線程:main 2
當(dāng)前線程:t1 3
main當(dāng)前線程:main 3
當(dāng)前線程:t1 4
main當(dāng)前線程:main 4
當(dāng)前線程:t2 0
當(dāng)前線程:t2 1
當(dāng)前線程:t2 2
當(dāng)前線程:t2 3
當(dāng)前線程:t2 4
main當(dāng)前線程:main 5
main當(dāng)前線程:main 6
main當(dāng)前線程:main 7
main當(dāng)前線程:main 8
main當(dāng)前線程:main 9

如果我們把join()方法注釋掉之后:

main當(dāng)前線程:main 0
當(dāng)前線程:t1 0
main當(dāng)前線程:main 1
當(dāng)前線程:t1 1
main當(dāng)前線程:main 2
當(dāng)前線程:t1 2
main當(dāng)前線程:main 3
當(dāng)前線程:t1 3
main當(dāng)前線程:main 4
當(dāng)前線程:t1 4
main當(dāng)前線程:main 5
main當(dāng)前線程:main 6
main當(dāng)前線程:main 7
main當(dāng)前線程:main 8
main當(dāng)前線程:main 9
當(dāng)前線程:t2 0
當(dāng)前線程:t2 1
當(dāng)前線程:t2 2
當(dāng)前線程:t2 3
當(dāng)前線程:t2 4

由上我們可以得出以下結(jié)論:

  • 在使用了join()方法之后主線程會等待子線程結(jié)束之后才會結(jié)束裸违。

方法詳解- setDaemon(boolean on),getDaemon()

用來設(shè)置是否為守護(hù)線程和判斷是否為守護(hù)線程掖桦。
notes:

  • 守護(hù)線程依賴于創(chuàng)建他的線程,而用戶線程則不需要供汛。如果在main()方法中創(chuàng)建了一個(gè)守護(hù)線程枪汪,那么當(dāng)main方法執(zhí)行完畢之后守護(hù)線程也會關(guān)閉涌穆。而用戶線程則不會,在JVM中垃圾收集器的線程就是守護(hù)線程雀久。

優(yōu)雅的終止線程

有三種方法可以終止線程宿稀,如下:

  1. 使用退出標(biāo)識,使線程正常的退出赖捌,也就是當(dāng)run()方法完成后線程終止祝沸。
  2. 使用stop()方法強(qiáng)行關(guān)閉,這個(gè)方法現(xiàn)在已經(jīng)被廢棄越庇,不推薦使用
  3. 使用interrupt()方法終止線程罩锐。

具體的實(shí)現(xiàn)代碼我將在下一篇博文中將到。悦荒。

線程的優(yōu)先級

在操作系統(tǒng)中線程是分優(yōu)先級的唯欣,優(yōu)先級高的線程CPU將會提供更多的資源,在java中我們可以通過setPriority(int newPriority)方法來更改線程的優(yōu)先級搬味。
在java中分為1~10這個(gè)十個(gè)優(yōu)先級境氢,設(shè)置不在這個(gè)范圍內(nèi)的優(yōu)先級將會拋出IllegalArgumentException異常。
java中有三個(gè)預(yù)設(shè)好的優(yōu)先級:

  • public final static int MIN_PRIORITY = 1;
  • public final static int NORM_PRIORITY = 5;
  • public final static int MAX_PRIORITY = 10;

參考

java多線程思維圖


總結(jié)

以上就是我總結(jié)的java多線程基礎(chǔ)知識碰纬,后續(xù)會補(bǔ)充線程關(guān)閉萍聊、線程狀態(tài)、線程同步和有返回結(jié)果的多線程悦析。

個(gè)人博客地址:http://crossoverjie.top寿桨。
GitHub地址:[https://github.com/crossoverJie]

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市强戴,隨后出現(xiàn)的幾起案子亭螟,更是在濱河造成了極大的恐慌,老刑警劉巖骑歹,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件预烙,死亡現(xiàn)場離奇詭異,居然都是意外死亡道媚,警方通過查閱死者的電腦和手機(jī)扁掸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來最域,“玉大人谴分,你說我怎么就攤上這事《浦” “怎么了牺蹄?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長薄翅。 經(jīng)常有香客問我钞馁,道長虑省,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任僧凰,我火速辦了婚禮,結(jié)果婚禮上熟丸,老公的妹妹穿的比我還像新娘训措。我一直安慰自己,他們只是感情好光羞,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布绩鸣。 她就那樣靜靜地躺著,像睡著了一般纱兑。 火紅的嫁衣襯著肌膚如雪呀闻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天潜慎,我揣著相機(jī)與錄音捡多,去河邊找鬼。 笑死铐炫,一個(gè)胖子當(dāng)著我的面吹牛垒手,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播倒信,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼科贬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鳖悠?” 一聲冷哼從身側(cè)響起榜掌,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎乘综,沒想到半個(gè)月后憎账,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瘾带,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年鼠哥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片看政。...
    茶點(diǎn)故事閱讀 39,739評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡朴恳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出允蚣,到底是詐尸還是另有隱情于颖,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布嚷兔,位于F島的核電站森渐,受9級特大地震影響做入,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜同衣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一竟块、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧耐齐,春花似錦浪秘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至辕翰,卻和暖如春夺衍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背喜命。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工沟沙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人渊抄。 一個(gè)月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓尝胆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親护桦。 傳聞我的和親對象是個(gè)殘疾皇子含衔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評論 2 354

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

  • 前言 多線程并發(fā)編程是Java編程中重要的一塊內(nèi)容,也是面試重點(diǎn)覆蓋區(qū)域二庵,所以學(xué)好多線程并發(fā)編程對我們來說極其重要...
    嘟爺MD閱讀 7,315評論 21 272
  • 線程概述 線程與進(jìn)程 進(jìn)程 ?每個(gè)運(yùn)行中的任務(wù)(通常是程序)就是一個(gè)進(jìn)程贪染。當(dāng)一個(gè)程序進(jìn)入內(nèi)存運(yùn)行時(shí),即變成了一個(gè)進(jìn)...
    閩越布衣閱讀 1,009評論 1 7
  • 本文主要講了java中多線程的使用方法催享、線程同步杭隙、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法因妙、概述等痰憎。 首先講...
    李欣陽閱讀 2,454評論 1 15
  • Java多線程學(xué)習(xí) [-] 一擴(kuò)展javalangThread類 二實(shí)現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 2,957評論 1 18
  • 寫在前面的話: 這篇博客是我從這里“轉(zhuǎn)載”的,為什么轉(zhuǎn)載兩個(gè)字加“”呢攀涵?因?yàn)檫@絕不是簡單的復(fù)制粘貼铣耘,我花了五六個(gè)小...
    SmartSean閱讀 4,730評論 12 45