Java線程

面試--線程

知乎--java中的多線程究竟在什么情況下使用?

知乎--多線程有什么用切诀??

菜鳥教程--多線程編程

Java線程的概念

Java內(nèi)置支持多線程編程(multithreaded programming),多線程程序包含2條或者2條以上并發(fā)運(yùn)行的部分搔弄,程序中每個(gè)這樣的部分叫做線程(thread),
每個(gè)線程都有獨(dú)立的運(yùn)行路徑幅虑。因此多線程任務(wù)是多任務(wù)處理的一種特殊形式。

進(jìn)程和線程的比較

  • 進(jìn)程:程序執(zhí)行時(shí)所占用的所有的資源的總稱或者說是容器
  • 線程:是基本執(zhí)行單元


    image

簡單總結(jié): 進(jìn)程是線程的容器顾犹。

進(jìn)程中的主線程

主線程的重要性體現(xiàn)在:

  • 他是產(chǎn)生其他子線程的線程
  • 通常它必須是最后完成執(zhí)行倒庵,因?yàn)樗鼘?zhí)行各種關(guān)閉操作

主線程是運(yùn)行程序時(shí)候自動(dòng)創(chuàng)建的,但是其也是一個(gè)Thread類的實(shí)例對(duì)象

一般由主線程創(chuàng)建其他子線程炫刷,然后由主線程慣例其他線程哄芜。

關(guān)于Thread類的基本使用

Java的多線程系統(tǒng)建立于Thread類和Runable接口. Thread類定義了好幾種方法來幫助管理線程:

方法 意義
getName 獲取線程名稱
getPriority 獲取線程優(yōu)先級(jí)
isAlive 判定線程是否仍在執(zhí)行
join 等待一個(gè)線程終止
run 線程的入口
sleep 在一定時(shí)間內(nèi)掛起線程
start 通過調(diào)用運(yùn)行方法來啟動(dòng)線程

繼承Thread類創(chuàng)建新的線程

  • 我們可以通過繼承Thread類,Override run()方法柬唯,然后創(chuàng)建該類的實(shí)例來創(chuàng)建除了主線程之外的其他線程认臊,并且在run()方法中賦予屬于這個(gè)線程的代碼邏輯,這個(gè)run()方法是新線程的入口锄奢。
  • 這個(gè)新建的Thread的子類失晴,會(huì)繼承Thread類的一個(gè)start()方法,調(diào)用start()方法時(shí)拘央,會(huì)自動(dòng)生成一個(gè)線程涂屁,然后調(diào)用run()方法執(zhí)行該線程的邏輯。
package com.DeeJay.ThreadDemo;

public class ThreadDemo extends Thread{ // 自定義線程類
//    運(yùn)行的代碼邏輯和主線程不同
    public ThreadDemo() {
//        創(chuàng)建一個(gè)新的子線程
        super("Child Thread");
//        顯式的開始執(zhí)行這個(gè)線程 ===> 做一些基本的初始化操作 ===> run()
        this.start();
    }

//    新的子線程的入口灰伟!
    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i ++) {
                System.out.println("Child Thread: " + i);
                Thread.sleep(1000);
            }
        }catch (Exception e) {
            System.out.println("Child Thread Error!");
        }
    }
}

package com.DeeJay;

import com.DeeJay.ThreadDemo.ThreadDemo;

public class Main {
    public static void main(String[] args) {
//        創(chuàng)建新的子線程拆又!
        ThreadDemo t = new ThreadDemo();
        // 需要注意的是此處代碼執(zhí)行并不會(huì)像單線程一樣阻塞,而是會(huì)立即執(zhí)行下面語句
//        調(diào)用完構(gòu)造函數(shù)和start()之后栏账, 子線程開始執(zhí)行帖族,主線程返回到Main(),開始自己的邏輯
//        主線程邏輯
        try {
            for (int i = 0; i < 5; i ++) {
                System.out.println("Main Thread: " + i);
                Thread.sleep(1000);
            }
        }catch (Exception e) {
            System.out.println("Main Thread Error!");
        }
    }
}

此處在定義新的線程類的時(shí)候,也可以不在構(gòu)造函數(shù)內(nèi)部直接執(zhí)行start(),可以等新的線程類的實(shí)例對(duì)象創(chuàng)建后手動(dòng)執(zhí)行start()開始子線程邏輯挡爵。

實(shí)現(xiàn)Runable接口來創(chuàng)建新的線程

package com.DeeJay.ThreadDemo;

public class ThreadDemo implements Runnable{ // 改為實(shí)現(xiàn)Runnable接口
    private Thread t;

    public ThreadDemo(String name) {
//        選用帶target的構(gòu)造函數(shù)竖般, 將this傳過去,本質(zhì)上就是把run()方法傳遞過去
        t = new Thread(this, name);
        t.start();
    }

    //    一樣的  實(shí)現(xiàn)run() 作為線程的入口
    @Override
    public void run() {
        for (int i = 0; i < 5; i ++) {
            System.out.println("Runnable Thread: " + i);
        }
        System.out.println(t.getName() + " existing");
    }
}

package com.DeeJay;

import com.DeeJay.ThreadDemo.ThreadDemo;

public class Main {
    public static void main(String[] args) {
        new ThreadDemo("Child Thread");
    }
}

線程同步

當(dāng)多個(gè)線程需要共享資源茶鹃,他們就需要某種方法來確定資源在某一刻僅被一個(gè)線程占用著涣雕。

達(dá)到上述目的的過程就叫做同步(synchronization)

synchronized方法的使用

當(dāng)一個(gè)線程在一個(gè)synchronized方法的內(nèi)部艰亮,所有試圖調(diào)用該方法(或者其他synchronized方法)的同一個(gè)實(shí)例對(duì)象的線程必須等待。為了退出管程挣郭,并釋放對(duì)對(duì)象的控制權(quán)給其他等待的線程迄埃,擁有管程的線程僅需從synchronized方法返return即可。

一般如果一個(gè)對(duì)象的方法可能被多線程使用兑障,則用synchronized關(guān)鍵字修飾

來看一個(gè)具體的使用synchronized方法的例子:
Callme.java

package com.DeeJay;

public class Callme {
    public void call(String msg) {
        System.out.print("[" + msg);
        try {
            Thread.sleep(1000);
        }catch (Exception e) {
            e.printStackTrace();
        }
        System.out.print("]");
    }
}

Caller.java

package com.DeeJay;

public class Caller implements Runnable{
    private String msg;
    Thread t;
    Callme target;

    public Caller(String msg, Callme target) {
        this.msg = msg;
        this.t = new Thread(this);
        this.target = target;
        t.start();
    }

    @Override
    public void run() {
        target.call(this.msg);
    }
}

Main

package com.DeeJay;

public class Main {
    public static void main(String[] args) {
        Callme target = new Callme(); // 共享的資源

        Caller c1 = new Caller("hello", target);
        Caller c2 = new Caller("world", target);
        Caller c3 = new Caller("synchronized", target);

        try {
            c1.t.join();
            c2.t.join();
            c3.t.join();
        }catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("\n");
        System.out.println("Main Thread End!");
    }
}

上述例子中侄非,主線程中new出來的target即為幾個(gè)子線程公用的資源。當(dāng)調(diào)用三個(gè)子線程時(shí)旺垒,三個(gè)子線程會(huì)并發(fā)執(zhí)行彩库,并不會(huì)等到一個(gè)執(zhí)行完再執(zhí)行下一個(gè)肤无,所以看到的輸出結(jié)果為:

[hello[world[synchronized]]]

我們想達(dá)到的效果是在一個(gè)子線程占用公用資源target時(shí)先蒋,不允許其他線程也使用該資源,為了達(dá)到這個(gè)效果宛渐,就可以給將target的這個(gè)call方法用synchronized修飾:
Callme.java

package com.DeeJay;

public class Callme {
    public synchronized void call(String msg) {
        System.out.print("[" + msg);
        try {
            Thread.sleep(1000);
        }catch (Exception e) {
            e.printStackTrace();
        }
        System.out.print("]");
    }
}

之后再運(yùn)行竞漾,可以看到輸出的為:

[hello][synchronized][world]

synchronized修飾語句塊

對(duì)于上述的例子,還可以使用synchronized修飾代碼塊來達(dá)到相同的效果窥翩,synchronized修飾的代碼塊就是同步執(zhí)行的业岁,我們可以不用在Callme類中修改call方法為synchronized,而是在調(diào)用Callme實(shí)例對(duì)象的Caller類中寇蚊,將代碼塊置為synchronized笔时,這樣代碼塊中的邏輯也達(dá)到了同步的效果。
Caller.java

package com.DeeJay;

public class Caller implements Runnable{
    private String msg;
    Thread t;
    Callme target;

    public Caller(String msg, Callme target) {
        this.msg = msg;
        this.t = new Thread(this);
        this.target = target;
        t.start();
    }

    @Override
    public void run() {
        synchronized (target) { // synchronized block
            target.call(this.msg);
        }
    }
}

關(guān)于join

關(guān)于join()方法仗岸,join方法的作用是允耿,當(dāng)我們調(diào)用某個(gè)線程的這個(gè)方法時(shí),這個(gè)方法會(huì)掛起調(diào)用線程扒怖,直到被調(diào)用線程結(jié)束執(zhí)行较锡,調(diào)用線程才會(huì)繼續(xù)執(zhí)行。

關(guān)鍵是理解掛起調(diào)用線程盗痒,首先舉個(gè)例子:
子線程:

package com.DeeJay.ThreadDemo;

public class ThreadDemo extends Thread{
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        }catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Child Thread End!");
    }
}

Main:

package com.DeeJay;

import com.DeeJay.ThreadDemo.ThreadDemo;

public class Main {
    public static void main(String[] args) {
        ThreadDemo t = new ThreadDemo();
        t.start();
        try {
            t.join(); // 
        }catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Main Thread End!");
    }
}

子線程的邏輯是等待1秒后輸出信息蚂蕴,當(dāng)調(diào)用join()后,子線程的調(diào)用線程此時(shí)是主線程俯邓,所以主線程會(huì)暫時(shí)掛起不繼續(xù)執(zhí)行骡楼,等待子線程執(zhí)行完成后,才會(huì)繼續(xù)執(zhí)行主線程稽鞭。這時(shí)候才輸出主線程中的信息君编。所以此時(shí)輸出信息應(yīng)該為:

// 一秒后輸出:
Child Thread End!
Main Thread End!

但是如果不調(diào)用子線程的join(),此時(shí)主線程并不會(huì)掛起川慌,會(huì)和子線程并發(fā)執(zhí)行吃嘿,主線程并沒有等待祠乃,所以會(huì)先于子線程執(zhí)行:
子線程:

package com.DeeJay;

import com.DeeJay.ThreadDemo.ThreadDemo;

public class Main {
    public static void main(String[] args) {
        ThreadDemo t = new ThreadDemo();
        t.start();
        try {
//            t.join(); // 不調(diào)用join 在子線程執(zhí)行的過程中 主線程并不會(huì)被掛起
        }catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Main Thread End!");
    }
}

此時(shí)輸出為:

Main Thread End!  // 馬上輸出
Child Thread End! // 過一秒后輸出

當(dāng)有多個(gè)子線程時(shí),如果前面的子線程先于后面的子線程調(diào)用start()之前調(diào)用join()的話兑燥,此時(shí)調(diào)用線程還是主線程亮瓷,主線程依然會(huì)掛起,所以后面子線程的start()的邏輯要等到前面子線程執(zhí)行完后主線程繼續(xù)開始執(zhí)行才能執(zhí)行到降瞳。所以會(huì)有單線程的表現(xiàn)形式嘱支,但是此時(shí)其實(shí)仍是多線程。

package com.DeeJay;

import com.DeeJay.ThreadDemo.ThreadDemo;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        ThreadDemo t1 = new ThreadDemo();
        ThreadDemo t2 = new ThreadDemo();
        t1.start();
        t1.join(); //  此處調(diào)用t1的join() 會(huì)掛起主線程  t1執(zhí)行結(jié)束后才喚醒主線程繼續(xù)執(zhí)行t2.start()
        t2.start();
        t2.join(); // 同理 t2開始執(zhí)行時(shí)掛起主線程 t2執(zhí)行完后喚醒主線程
        System.out.println("Main Thread End!");
    }
}

輸出結(jié)果為:

Child Thread End! // 過1秒
Child Thread End! // 過2秒
Main Thread End! // 過2秒 

線程之間的通信

以下方法用來和其他線程進(jìn)行交流, 并且只能在synchronize修飾的代碼塊或者方法里調(diào)用:

wait( ) 告知被調(diào)用的線程放棄管程進(jìn)入睡眠直到其他線程進(jìn)入相同管程并且調(diào)用notify( )挣饥。
notify( ) 恢復(fù)相同對(duì)象中第一個(gè)調(diào)用 wait( ) 的線程除师。
notifyAll( ) 恢復(fù)相同對(duì)象中所有調(diào)用 wait( ) 的線程。具有最高優(yōu)先級(jí)的線程最先運(yùn)行扔枫。

package com.DeeJay.ThreadDemo;

public class Q {
    private int n; // 生產(chǎn)的對(duì)象
    private boolean isDataReady = false;

    public int get() throws InterruptedException {
        if(!isDataReady){
            wait(); // 暫停, 等待著put()來設(shè)置好這個(gè)值只后再讀取
        }
        System.out.println("Get " + n);
        isDataReady = false;  // 標(biāo)記已經(jīng)取走了值
        notify(); // 通知put()可以設(shè)置另外一個(gè)值了
        return this.n;
    }

    public void put(int n) throws InterruptedException {
        // 如果這個(gè)值已經(jīng)設(shè)置好了
        if (isDataReady) {
            wait(); // 等待get()把值取走
        }
        this.n = n; //設(shè)置一個(gè)新的值
        isDataReady = true; // 標(biāo)記已經(jīng)設(shè)置好了
        System.out.println("Put: " + n);
        notify(); // 通知線程get() 這個(gè)最新的值
    }
}

關(guān)于死鎖

死鎖發(fā)生在當(dāng)兩個(gè)線程對(duì)一對(duì)同步對(duì)象有循環(huán)依賴關(guān)系時(shí)

比如說有ABC三個(gè)子線程汛聚,A依賴于B,B依賴于C短荐,C依賴于A倚舀,就有可能出現(xiàn)死鎖。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末忍宋,一起剝皮案震驚了整個(gè)濱河市痕貌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌糠排,老刑警劉巖舵稠,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異入宦,居然都是意外死亡哺徊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門云石,熙熙樓的掌柜王于貴愁眉苦臉地迎上來唉工,“玉大人,你說我怎么就攤上這事汹忠×芟酰” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵宽菜,是天一觀的道長谣膳。 經(jīng)常有香客問我,道長铅乡,這世上最難降的妖魔是什么继谚? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮阵幸,結(jié)果婚禮上花履,老公的妹妹穿的比我還像新娘芽世。我一直安慰自己,他們只是感情好诡壁,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布济瓢。 她就那樣靜靜地躺著,像睡著了一般妹卿。 火紅的嫁衣襯著肌膚如雪旺矾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天夺克,我揣著相機(jī)與錄音箕宙,去河邊找鬼。 笑死铺纽,一個(gè)胖子當(dāng)著我的面吹牛柬帕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播室囊,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼雕崩,長吁一口氣:“原來是場噩夢啊……” “哼魁索!你這毒婦竟也來了融撞?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤粗蔚,失蹤者是張志新(化名)和其女友劉穎尝偎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鹏控,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡致扯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了当辐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抖僵。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖缘揪,靈堂內(nèi)的尸體忽然破棺而出耍群,到底是詐尸還是另有隱情,我是刑警寧澤找筝,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布蹈垢,位于F島的核電站,受9級(jí)特大地震影響袖裕,放射性物質(zhì)發(fā)生泄漏曹抬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一急鳄、第九天 我趴在偏房一處隱蔽的房頂上張望谤民。 院中可真熱鬧堰酿,春花似錦、人聲如沸张足。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽兢榨。三九已至嗅榕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吵聪,已是汗流浹背凌那。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吟逝,地道東北人帽蝶。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像块攒,于是被迫代替她去往敵國和親励稳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

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

  • 前言:雖然自己平時(shí)都在用多線程囱井,也能完成基本的工作需求驹尼,但總覺得,還是對(duì)線程沒有一個(gè)系統(tǒng)的概念庞呕,所以新翎,查閱了一些資...
    justCode_閱讀 701評(píng)論 0 9
  • 【JAVA 線程】 線程 進(jìn)程:是一個(gè)正在執(zhí)行中的程序。每一個(gè)進(jìn)程執(zhí)行都有一個(gè)執(zhí)行順序住练。該順序是一個(gè)執(zhí)行路徑地啰,或者...
    Rtia閱讀 2,764評(píng)論 2 20
  • Java中的線程(多線程),本篇主要講一下線程的概念和基本操作以及各個(gè)方法的用法等讲逛;首先在了解線程前我們必須應(yīng)該知...
    星星_點(diǎn)燈閱讀 369評(píng)論 0 0
  • 不管你是新程序員還是老手亏吝,你一定在面試中遇到過有關(guān)線程的問題。Java語言一個(gè)重要的特點(diǎn)就是內(nèi)置了對(duì)并發(fā)的支持盏混,讓...
    堯淳閱讀 1,590評(píng)論 0 25
  • 偶然地看到小白營的推文蔚鸥,文字有趣內(nèi)容入心,便花9塊錢推門進(jìn)來了括饶,沒作大的期待株茶,卻沒成想由此推開了一個(gè)精彩的...
    lucy李慧霞閱讀 340評(píng)論 0 12