Java多線程編程

進程與線程

??在Java語言里面最大的特點是支持多線程的開發(fā)(也是為數(shù)不多支持多線程的編程語言),所以在整個Java技術(shù)的學習里面,如果你不能對多線程的概念有一個全面并且細致的了解秘遏,則在日后進行 一些項目設(shè)計尤其是并發(fā)訪問設(shè)計的過程之中就會出現(xiàn)嚴重的技術(shù)缺陷。
??如果想要理解線程近弟,就得先了解進程的概念祝峻,在傳統(tǒng)的DOS系統(tǒng)中,其本身有一個特征:如果你電腦上出現(xiàn)了病毒堵漱,那么所有的程序?qū)o法執(zhí)行综慎,因為傳統(tǒng)的DOS采用的是單線程處理,而單進程處理的最大特點:在同一個時間段上勤庐,只允許一個程序在執(zhí)行示惊。
??那么后來來到了Windows的時代就開啟了許多多線程的設(shè)計,于是就表示在同一個時間段上可以同時運行多個程序愉镰,并且這些將進行資源的輪流搶占米罚。所以在同一個時間段上會有對個程序一次執(zhí)行,但是在同一個時間點上只會有一個進程執(zhí)行丈探,而后來到了多核CPU录择,由于可以處理的CPU多了,即便有再多的進程,也可以比單核CPU處理的速度有所提升隘竭。
??線程是在進程基礎(chǔ)之上劃分的更小的程序單元塘秦,線程是在進程基礎(chǔ)上創(chuàng)建并使用的,所以線程依賴于進程的支持货裹,但是線程的啟動速度要比進程快許多嗤形,所以當使用多線程進行并發(fā)處理時,其執(zhí)行性能要高于進程弧圆。
??Java是多線程的編程語言赋兵,所以Java在進行并發(fā)訪問處理的時候可以得到更高的處理性能。
??如果想在Java中實現(xiàn)多線程的定義搔预,那么就需要有一個專門的線程主體類進行線程的執(zhí)行任務(wù)的定義霹期,而這個主體類的定義是有要求的,不許是實現(xiàn)特定的接口或者繼承特定的父類才可以完成拯田。

繼承Thead類實現(xiàn)多線程

??Java里面有一個java.lang.Thread的程序類历造,那么一個只要繼承了此類就表示這個類為我們線程的主體類,但是并不是說這個類就可以實現(xiàn)多線程處理船庇,因為還需要覆寫Thread類中提供的一個run()方法吭产,而這個方法就屬于線程的主方法。
范例:多線程主體類

class MyThread extends Thread {//線程的主體類
    private String title;
    public MyThread(String title) {
        this.title = title;
    }
    @Override
    public void run() {//線程的主體方法
        for (int i = 0; i < 10; i++) {
            System.out.printf("%s運行.i = %s \n", this.title, i);
        }
    }
}

??多線程要執(zhí)行的功能都應(yīng)該在run()方法中進行定義鸭轮。
??需要說明的是:在正常情況下臣淤,如果想使用一個類中的方法,那么肯定要產(chǎn)生實例化對象窃爷,而后去調(diào)用類中提供的方法邑蒋,但是run()方法是不能夠直接被調(diào)用的,因為這里面牽扯到操作系統(tǒng)資源調(diào)度問題按厘,所以要想啟動多線程必須使用start()方法完成塞颁。
范例:多線程啟動

public class ThreadDemo {
    public static void main(String[] args) {
        new MyThread("線程A").start();
        new MyThread("線程B").start();
        new MyThread("線程C").start();
    }
}

??通過此時的調(diào)用你可以發(fā)現(xiàn)试疙,雖然調(diào)用了start()方法疮绷,但是最終執(zhí)行的是run()方法嘱根,并且所有的線程對象都是交替執(zhí)行的。
??疑問:為什么多線程的啟動不直接使用run()方法而必須使用Thread類中的start()方法呢懒棉?如果要想清楚這個問題草描,最好的做法是查看一下start()方法的實現(xiàn)操作,可以直接通過源代碼進行觀察漓藕。

   public synchronized void start() {
        if (threadStatus != 0) //判斷線程的狀態(tài)
            throw new IllegalThreadStateException();//拋出一個異常
        group.add(this);
        boolean started = false;
        try {
            start0();//在start()方法中調(diào)用了start0()
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
            }
        }
    }
    private native void start0();//只定義了方法名稱陶珠,但沒有實現(xiàn)

??發(fā)現(xiàn)start()方法里面會拋出一個IllegalThreadStateException異常類對象,但是整個程序中并沒有使用throws或者是明確的try..catch處理享钞,因為該異常一定是RuntimeException的子類揍诽,每一個線程類的對象只允許啟動一次诀蓉,如果重復啟動則拋出IllegalThreadStateException異常,例如:下面的代碼就會拋出異常暑脆。

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread mt=  new MyThread("線程A");
        mt.start();
        mt.start();//重復進行了線程的啟動
    }
}
Exception in thread "main" java.lang.IllegalThreadStateException

??在Java程序執(zhí)行的過程之中渠啤,考慮到對于不同層次開發(fā)者的需求,所以其支持有本地的操作系統(tǒng)函數(shù)調(diào)用添吗,而這項技術(shù)就被稱為JNI(Java Native Inteface)技術(shù)沥曹,但是Java開發(fā)過程中并不推薦這樣使用,利用這項技術(shù)可以使用一些操作系統(tǒng)提供的底層函數(shù)碟联,進行一些特處理妓美,而在Thread類中提供的start0()就表示需要將此方法依賴于不同的操作系統(tǒng)實現(xiàn)。


Thread的執(zhí)行分析

??任何情況下鲤孵,只要定義了多線程壶栋,多線程的啟動永遠只有一種方案:Thread類中的start()方法。

基于Runnable接口實現(xiàn)多線程

??雖然可以通過Thread類的繼承來實現(xiàn)多線程的定義普监,但是在Java程序中對于繼承永遠都是存在單繼承的局限的贵试,所以在Java中又提供第二種多線程的主體定義結(jié)構(gòu)形式:實現(xiàn)java.lang.Runnable接口,此接口定義如下:

@FunctionalInterface //從JDK1.8引入Lambda表達式后就變?yōu)榱撕瘮?shù)式的接口
public interface Runnable{
    public void run();
}

范例:通過Runnable實現(xiàn)多線程主體類

class MyThread implements Runnable {//線程的主體類
    private String title;
    public MyThread(String title) {
        this.title = title;
    }
    @Override
    public void run() {//線程的主體方法
        for (int i = 0; i < 10; i++) {
            System.out.printf("%s運行.i = %s \n", this.title, i);
        }
    }
}

??但是由于此時不再繼承Thread父類了凯正,那么對于此時的MyThread類中也就不再支持有start()這個繼承的方法毙玻,可是如果不用start()方法是無法進行多線程啟動的,那么這個時候就需要去觀察一下Thread類所提供的構(gòu)造方法廊散。

  • 構(gòu)造方法:public Thread?(Runnable target);
    范例:啟動多線程
public class ThreadDemo {
    public static void main(String[] args) {
        Thread threadA=new Thread(new MyThread("線程對象A"));
        Thread threadB=new Thread(new MyThread("線程對象B"));
        Thread threadC=new Thread(new MyThread("線程對象C"));
        threadA.start();//啟動多線程
        threadB.start();//啟動多線程
        threadC.start();//啟動多線程
    }
}

??這個時候的多線程實現(xiàn)中可以發(fā)現(xiàn)桑滩,由于只是實現(xiàn)了Runnable接口對象,所以此時線程主體類就不再有單繼承局限奸汇,這樣的設(shè)計才是一個標準型的設(shè)計施符。
??可以發(fā)現(xiàn)從JDK1.8開始往声,Runnable接口使用了函數(shù)式接口定義擂找,所以也可以直接使用Lambda表達式進行線程類實現(xiàn)。
范例:利用Lambda實現(xiàn)多線程定義

public class ThreadDemo {
    public static void main(String[] args) {
        for (int i = 1; i <= 3; i++) {
            String title = "線程對象" + i;
//            Runnable run = () -> {
//                for (int j = 0; j < 10; j++) {
//                    System.out.printf("%s運行.j = %s \n", title, j);
//                }
//            };
//            new Thread(run).start();
            new Thread(() -> {
                for (int j = 0; j < 10; j++) {
                    System.out.printf("%s運行.j = %s \n", title, j);
                }
            }).start();
        }
    }
}

??在以后的開發(fā)之中對于多線程的實現(xiàn)浩销,優(yōu)先考慮的就是Runnable接口實現(xiàn)贯涎,并且通過Thread類啟動多線程。

Thread與Runnable關(guān)系

??經(jīng)過一些列的分析之后可以發(fā)現(xiàn)慢洋,在多線程的實現(xiàn)過程之中已經(jīng)有了兩種做法:Thread類塘雳、Runnable接口,如果從代碼本身來講普筹,肯定是用Runnable是最方便的败明,因為其可以避免單繼承的局限,同事也可以更好的進行功能的擴充太防。
??但是從結(jié)構(gòu)上也需要來觀察Thread和Runnable的聯(lián)系妻顶,打開Thead的定義:

public class Thread extends Object implements Runnable {}

??發(fā)現(xiàn)Thread類也是Runnable接口的子類,所以繼承Thread類時覆寫的還是Runnable接口的run(),于是此時觀察一下程序的類結(jié)構(gòu)讳嘱。

class MyThread implements Runnable {//線程的主體類
    private String title;
    public MyThread(String title) {
        this.title = title;
    }
    @Override
    public void run() {//線程的主體方法
        for (int i = 0; i < 10; i++) {
            System.out.printf("%s運行.i = %s \n", this.title, i);
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        Thread threadA=new Thread(new MyThread("線程對象A"));
        Thread threadB=new Thread(new MyThread("線程對象B"));
        Thread threadC=new Thread(new MyThread("線程對象C"));
        threadA.start();//啟動多線程
        threadB.start();//啟動多線程
        threadC.start();//啟動多線程
    }
}
Thread與Runnable

??多線程的設(shè)計之中幔嗦,使用了代理設(shè)計模式的結(jié)構(gòu),用戶自定義的線程主體只是負責項目核心的實現(xiàn)沥潭,而所有輔助實現(xiàn)全部由Thread類處理邀泉。
??在進行Thread啟動多線程的時候調(diào)用的是start()方法,而后找到的是run()方法钝鸽,但通過Thread類的構(gòu)造方法傳遞了一個Runnable接口對象時汇恤,那么該接口對象將被Thread類中的target屬性所保存,在start()方法執(zhí)行時會調(diào)用Thread中的run()方法拔恰,而這個run()方法會去調(diào)用Runnable接口子類被覆寫過的run()方法屁置。
??多線程開發(fā)的本質(zhì)實質(zhì)上是在于多個線程可以進行同一資源的搶占,那么Thread主要描述的是線程仁连,而資源的描述是通過Runnable完成的蓝角。


多線程開發(fā)

范例:利用賣票程序來實現(xiàn)多個線程的資源并發(fā)訪問

class MyThread implements Runnable {//線程的主體類
    private int ticket = 5;
    @Override
    public void run() {//線程的主體方法
        for (int i = 0; i < 100; i++) {
            if (this.ticket > 0)
                System.out.printf("賣票,ticket = %s \n", this.ticket--);
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        new Thread(mt).start();//第一個線程啟動
        new Thread(mt).start();//第二個線程啟動
        new Thread(mt).start();//第三個線程啟動
    }
}

??通過內(nèi)存分析圖來分析本程序的執(zhí)行結(jié)構(gòu)饭冬。


內(nèi)存圖

Callable實現(xiàn)多線程

??從最傳統(tǒng)的開發(fā)來說使鹅,如果要進行多線程的實現(xiàn)肯定依靠Runnable,但是Runnable接口有一個缺點昌抠,當Runnable執(zhí)行完成后無法獲取一個返回值患朱,所以從JDK1.5后提出了一個新的線程實現(xiàn)接口:java.util.concurrent.Callable接口,首先來觀察這個接口的定義:

@FunctionalInterface
public interface Callable<V>{
    public V call() throws Exception;
}

??可以發(fā)現(xiàn)Callable定義的時候可以設(shè)置一個泛型炊苫,此泛型的類型就是返回數(shù)據(jù)的類型裁厅,這樣的好處在于避免向下轉(zhuǎn)型所帶來的的安全隱患。


Callable

范例:使用Callable實現(xiàn)多線程處理

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

class MyThread implements Callable<String> {//線程的主體類
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 10; i++) {
            System.out.println("*********** 線程侨艾、i = " + i);
        }
        return "線程執(zhí)行完畢执虹。";
    }
}
public class ThreadDemo {
    public static void main(String[] args) throws Exception {
        FutureTask<String> task = new FutureTask(new MyThread());
        new Thread(task).start();
        System.out.println("【線程返回數(shù)據(jù)】" + task.get());
    }
}

線程運行狀態(tài)

??對于多線程的開發(fā)而言,編寫程序的過程之中總是按照:定義線程主體類唠梨,然后通過Thread類進行線程的啟動袋励,但是并不意味著你調(diào)用了start()方法,線程就已經(jīng)開始運行了当叭,因為整體的線程處理有自己的一套運行的狀態(tài)茬故。


線程運行狀態(tài)圖

1、任何一個線程的對象都應(yīng)該使用Thread類進行封裝蚁鳖,所以線程的啟動使用的是start()磺芭,但是啟動的時候?qū)嶋H上若干個線程都將進入到一種就緒狀態(tài),現(xiàn)在并沒有執(zhí)行醉箕;
2钾腺、進入到就緒狀態(tài)后就需要等待進行資源調(diào)度甘邀,當某一個線程調(diào)度成功之后則進入到運行狀態(tài)(run()方法),但是所有的線程不可能一直持續(xù)執(zhí)行下去垮庐,中間需要產(chǎn)生一些暫停的狀態(tài)松邪,例如:某個線程執(zhí)行一段時間之后就將需要讓出資源,而后這個線程就將進入到阻塞狀態(tài)哨查,隨后重新回歸到就緒狀態(tài)逗抑。
3寒亥、當run()方法執(zhí)行完畢之后溉奕,實際上該線程的主要任務(wù)也就結(jié)束了,那么此時就可以直接進入到停止狀態(tài)加勤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鳄梅,一起剝皮案震驚了整個濱河市戴尸,隨后出現(xiàn)的幾起案子孙蒙,更是在濱河造成了極大的恐慌挎峦,老刑警劉巖翅阵,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異岖圈,居然都是意外死亡蜂科,警方通過查閱死者的電腦和手機导匣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進店門赋访,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人旋炒,你說我怎么就攤上這事鼎兽〗幽危” “怎么了序宦?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵行剂,是天一觀的道長厚宰。 經(jīng)常有香客問我铲觉,道長灯荧,這世上最難降的妖魔是什么逗载? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮秧均,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘誉己。我一直安慰自己,他們只是感情好筑累,可當我...
    茶點故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布奔穿。 她就那樣靜靜地躺著,像睡著了一般贱田。 火紅的嫁衣襯著肌膚如雪缅茉。 梳的紋絲不亂的頭發(fā)上男摧,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天,我揣著相機與錄音,去河邊找鬼蔬蕊。 笑死们妥,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的煮盼。 我是一名探鬼主播僵控,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鱼冀!你這毒婦竟也來了充易?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤荸型,失蹤者是張志新(化名)和其女友劉穎盹靴,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瑞妇,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡鹉究,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了踪宠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片自赔。...
    茶點故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖柳琢,靈堂內(nèi)的尸體忽然破棺而出绍妨,到底是詐尸還是另有隱情,我是刑警寧澤柬脸,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布他去,位于F島的核電站,受9級特大地震影響倒堕,放射性物質(zhì)發(fā)生泄漏灾测。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一垦巴、第九天 我趴在偏房一處隱蔽的房頂上張望媳搪。 院中可真熱鬧铭段,春花似錦、人聲如沸秦爆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽等限。三九已至爸吮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間望门,已是汗流浹背形娇。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留筹误,地道東北人桐早。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像纫事,于是被迫代替她去往敵國和親勘畔。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,665評論 2 354