JAVA多線程(一)

本篇博客主要介紹線程概念、線程的創(chuàng)建讯沈、線程狀態(tài)以及線程的常用方法凝果!

一祝迂、基本概念

程序(program): 是為完成特定任務(wù)、用某種語(yǔ)言編寫(xiě)的一組指令的集合器净。即指一 段靜態(tài)的代碼型雳,靜態(tài)對(duì)象。

進(jìn)程(process):是程序的一次執(zhí)行過(guò)程山害,或是正在運(yùn)行的一個(gè)程序纠俭。是一個(gè)動(dòng)態(tài) 的過(guò)程:有它自身的產(chǎn)生、存在和消亡的過(guò)程浪慌≡┚#——生命周期

  • 運(yùn)行中的QQ,運(yùn)行中的MP3播放器

  • 程序是靜態(tài)的权纤,進(jìn)程是動(dòng)態(tài)的

  • 進(jìn)程作為資源分配的單位钓简,系統(tǒng)在運(yùn)行時(shí)會(huì)為每個(gè)進(jìn)程分配不同的內(nèi)存區(qū)域

線程(thread):進(jìn)程可進(jìn)一步細(xì)化為線程乌妒,是一個(gè)程序內(nèi)部的一條執(zhí)行路徑。

  • 若一個(gè)進(jìn)程同一時(shí)間并行執(zhí)行多個(gè)線程涌庭,就是支持多線程的

  • 線程作為調(diào)度和執(zhí)行的單位芥被,每個(gè)線程擁有獨(dú)立的運(yùn)行棧和程序計(jì)數(shù)器(pc),線程切換的開(kāi) 銷(xiāo)小

  • 一個(gè)進(jìn)程中的多個(gè)線程共享相同的內(nèi)存單元/內(nèi)存地址空間->它們從同一堆中分配對(duì)象坐榆,可以 訪問(wèn)相同的變量和對(duì)象拴魄。這就使得線程間通信更簡(jiǎn)便、高效席镀。但多個(gè)線程操作共享的系統(tǒng)資 源可能就會(huì)帶來(lái)安全的隱患匹中。

線程與進(jìn)程.png

單核CPU與多核CPU

  • 單核CPU,其實(shí)是一種假的多線程豪诲,因?yàn)樵谝粋€(gè)時(shí)間單元內(nèi)顶捷,也只能執(zhí)行一個(gè)線程 的任務(wù)。例如:雖然有多車(chē)道屎篱,但是收費(fèi)站只有一個(gè)工作人員在收費(fèi)服赎,只有收了費(fèi) 才能通過(guò),那么CPU就好比收費(fèi)人員交播。如果有某個(gè)人不想交錢(qián)重虑,那么收費(fèi)人員可以 把他“掛起”(晾著他,等他想通了秦士,準(zhǔn)備好了錢(qián)缺厉,再去收費(fèi))。但是因?yàn)镃PU時(shí) 間單元特別短隧土,因此感覺(jué)不出來(lái)提针。

  • 如果是多核的話,才能更好的發(fā)揮多線程的效率曹傀。(現(xiàn)在的服務(wù)器都是多核的)

  • 一個(gè)Java應(yīng)用程序java.exe辐脖,其實(shí)至少有三個(gè)線程:main()主線程,gc() 垃圾回收線程皆愉,異常處理線程揖曾。當(dāng)然如果發(fā)生異常,會(huì)影響主線程亥啦。

并行與并發(fā)

并行:多個(gè)CPU同時(shí)執(zhí)行多個(gè)任務(wù)。比如:多個(gè)人同時(shí)做不同的事

并發(fā):一個(gè)CPU(采用時(shí)間片)同時(shí)執(zhí)行多個(gè)任務(wù)练链。比如:秒殺翔脱、多個(gè)人做同一件事

二、線程的創(chuàng)建(三種方式)

1.繼承Thread類(lèi)(重點(diǎn))

  1. 自定義線程類(lèi)繼承Thread類(lèi)
  2. 重寫(xiě)Thread類(lèi)的run方法
  3. 創(chuàng)建自定義線程類(lèi)對(duì)象媒鼓,調(diào)用start()方法
// 創(chuàng)建線程方式一:繼承Thread類(lèi)届吁,實(shí)現(xiàn)run()方法错妖, 調(diào)用start()方法開(kāi)啟線程
// 總結(jié):線程開(kāi)啟之后不一定立即執(zhí)行,由CPU調(diào)度執(zhí)行
public class TestThread1 extends Thread {
    @Override
    public void run() {
        // run方法:線程體
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代碼"+i);
        }
    }

    public static void main(String[] args) {
        // 創(chuàng)建一個(gè)線程對(duì)象
        TestThread1 testThread1 = new TestThread1();
        // 調(diào)用start方法
        testThread1.start();
        // 主方法:main線程
        for (int i = 0; i < 200; i++) {
            System.out.println("我在學(xué)習(xí)多線程"+i);
        }
    }
}

2.實(shí)現(xiàn)Runnable接口(重點(diǎn))

1.定義一個(gè)類(lèi)實(shí)現(xiàn)Runnable接口
2.實(shí)現(xiàn)Runnable接口的run()方法
3.新建該類(lèi)的線程類(lèi)對(duì)象疚沐,調(diào)用start()方法

// 創(chuàng)建線程方式二:實(shí)現(xiàn)runnable接口; 重寫(xiě)run()方法暂氯;創(chuàng)建Thread對(duì)象(需要丟入Runnable接口實(shí)現(xiàn)類(lèi)),調(diào)用start()方法開(kāi)啟線程
public class TestThread3 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代碼" + i);
        }
    }

    public static void main(String[] args) {
        // 創(chuàng)建一個(gè)Runnable接口實(shí)現(xiàn)類(lèi)的對(duì)象
        TestThread3 thread3 = new TestThread3();
        // 創(chuàng)建一個(gè)線程對(duì)象亮蛔,通過(guò)線程對(duì)象來(lái)開(kāi)啟我們的線程痴施,代理
        Thread thread = new Thread(thread3);
        thread.start();

        for (int i = 0; i < 20; i++) {
            System.out.println("我在學(xué)習(xí)多線程" + i);
        }
    }
}

3.繼承Callable接口(了解)

1.創(chuàng)建一個(gè)類(lèi)實(shí)現(xiàn)Callable接口
2.實(shí)現(xiàn)Callable接口的call方法
3.創(chuàng)建實(shí)現(xiàn)Callable接口的類(lèi)對(duì)象
4.創(chuàng)建執(zhí)行服務(wù)ExecutorService service = Executors.newFixedThreadPool(線程數(shù))
5.提交使得線程進(jìn)入就緒狀態(tài)Future<> r1 = service .submit(實(shí)現(xiàn)Callable接口的類(lèi))
6.獲取提交結(jié)果 rs1 = r1.get()
7.關(guān)閉服務(wù)service.shutdown()

// 創(chuàng)建線程方式三:實(shí)現(xiàn)Callable接口
public class TestCallable implements Callable<Boolean> {

    private String name;
    private String url;

    public TestCallable(String url, String name) {
        this.name = name;
        this.url = url;
    }

    @Override
    public Boolean call() throws Exception {
        WebDownLoader webDownLoader = new WebDownLoader();
        webDownLoader.downLoad(this.url, this.name);
        System.out.println(this.name + "下載完成!");
        return true;

    }

    public static void main(String[] args) throws Exception {
        TestCallable t1 = new TestCallable("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2800189401,4260987560&fm=26&gp=0.jpg", "1.jpg");
        TestCallable t2 = new TestCallable("https://ns-strategy.cdn.bcebos.com/ns-strategy/upload/fc_big_pic/part-00527-1400.jpg", "2.jpg");
        TestCallable t3 = new TestCallable("https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=760070108,1900073437&fm=26&gp=0.jpg", "3.jpg");
        // 創(chuàng)建執(zhí)行服務(wù)
        ExecutorService ser = Executors.newFixedThreadPool(3);
        // 提交執(zhí)行
        Future<Boolean> r1 = ser.submit(t1);
        Future<Boolean> r2 = ser.submit(t2);
        Future<Boolean> r3 = ser.submit(t3);
        // 獲取結(jié)果
        boolean rs1 = r1.get();
        boolean rs2 = r2.get();
        boolean rs3 = r3.get();
        // 關(guān)閉服務(wù)
        ser.shutdown();
    }
}
class WebDownLoader {
    public void downLoad(String url, String name) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

三、線程狀態(tài)

線程狀態(tài)圖:


線程狀態(tài)圖.png

下面我們結(jié)合這張圖來(lái)介紹線程的幾個(gè)狀態(tài)以及幾個(gè)常用指令

1.線程的停止

jdk雖然提供了兩個(gè)線程停止的方法stop()destory(),但是JDK并不建議我們這樣做究流,在實(shí)際的操縱過(guò)程中辣吃,我們一般讓線程正常停止或者設(shè)置標(biāo)志位來(lái)使得線程停止

// 測(cè)試停止
// 1.建議線程正常停止  ---> 利用次數(shù),不建議使用死循環(huán)
// 2.建議使用標(biāo)志位  ---> 設(shè)置一個(gè)標(biāo)志位
// 3.不要使用stop或者destory等過(guò)時(shí)的方法或者jdk不建議使用的方法
public class TestStop implements Runnable {
    // 線程停止標(biāo)志位
    private boolean flag = true;
    @Override
    public void run() {
        int i = 0;
        while(this.flag) {
            System.out.println(Thread.currentThread().getName() + ":" + i ++ );
        }
    }

    // 停止線程方法
    public void stop() {
        this.flag = false;
    }

    public static void main(String[] args) {
        TestStop testStop = new TestStop();
        Thread thread = new Thread(testStop, "子線程1");
        thread.start();

        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
            if(i == 900) {
                testStop.stop();
                System.out.println("子線程停止了芬探!");
            }
        }
    }
}

2.線程休眠

通過(guò)調(diào)用Thread.sleep(休眠毫秒數(shù))方法神得,就可實(shí)現(xiàn)線程的休眠,線程進(jìn)入阻塞狀態(tài)偷仿。
線程休眠方法可以模擬計(jì)時(shí)以及模擬網(wǎng)絡(luò)延時(shí)的問(wèn)題
注意:線程進(jìn)入阻塞狀態(tài)之后哩簿,不會(huì)釋放鎖!

// 模擬倒計(jì)時(shí)
public class TestSleep2 {

    public static void tenDown() throws InterruptedException {
        int num = 10;
        while(num > 0) {
            Thread.sleep(1000);
            System.out.println(num--);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        tenDown();
    }
}

3.線程禮讓?zhuān)▂ield)

  • 讓當(dāng)前正在執(zhí)行的線程暫停但不阻塞
  • 將線程從運(yùn)行狀態(tài)變?yōu)榫途w狀態(tài)
  • 讓CPU重新調(diào)度酝静,禮讓不一定成功节榜,看CPU心情
public class TestYield {

    public static void main(String[] args) {
        MyYield myYield = new MyYield();
        new Thread(myYield, "a").start();
        new Thread(myYield, "b").start();
    }
}

class MyYield implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "線程開(kāi)始!");
        Thread.yield();
        System.out.println(Thread.currentThread().getName() + "線程結(jié)束形入!");
    }
}

多次運(yùn)行結(jié)果如下:


結(jié)果一.png

結(jié)果二.png

從運(yùn)行結(jié)果可以看出全跨,每次運(yùn)行結(jié)果都不一樣,禮讓不一定成功亿遂,需要看CPU的調(diào)度浓若!

4.線程合并(join,這里我認(rèn)為翻譯為線程插隊(duì)更合適)

  • join合并線程,待此線程執(zhí)行完畢之后再執(zhí)行其他線程蛇数,該線程在執(zhí)行過(guò)程中使得其他線程阻塞挪钓!
  • 可以想象成插隊(duì)
public class TestJoin implements Runnable{


    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("線程VIP來(lái)了" + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);
        thread.start();

        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
            if(i==50) {
                thread.join();   // 線程來(lái)插隊(duì)了
            }
        }
    }
}

5. 線程狀態(tài)檢測(cè)

jdk文檔中線程包含如下六種狀態(tài):


線程狀態(tài).png

調(diào)用線程對(duì)象的getState()方法即可獲得線程狀態(tài)!

public class TestState {

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("http://////");
        });

        Thread.State state =  thread.getState();
        thread.start();

       while(Thread.State.TERMINATED != state) {
            System.out.println(state);
            try {
                Thread.sleep(1000);
                state = thread.getState();
                System.out.println(state);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

6.線程優(yōu)先級(jí)(Priority)

  • 通過(guò)調(diào)用線程對(duì)象的setPriority()方法實(shí)現(xiàn)
  • 如果要設(shè)置線程優(yōu)先級(jí)耳舅,在調(diào)用start()方法之前設(shè)置優(yōu)先級(jí)
  • 線程的優(yōu)先級(jí)大于等于10小于等于1
  • 線程的優(yōu)先級(jí)高不一定就先運(yùn)行碌上,運(yùn)行順序具體還要看CPU的調(diào)度
public class TestPriority extends Thread {

    public static void main(String[] args) {
        // 查看Main函數(shù)的優(yōu)先級(jí)
        System.out.println("main--->" + Thread.currentThread().getPriority());


        MyPriority myPriority = new MyPriority();

        Thread t1 = new Thread(myPriority, "線程1");
        Thread t2 = new Thread(myPriority, "線程2");
        Thread t3 = new Thread(myPriority, "線程3");
        Thread t4 = new Thread(myPriority, "線程4");
        Thread t5 = new Thread(myPriority, "線程5");
        Thread t6 = new Thread(myPriority, "線程6");

        t1.start();

        // 如果要設(shè)置優(yōu)先級(jí),先設(shè)置優(yōu)先級(jí)浦徊,再啟動(dòng)
        // 線程的優(yōu)先級(jí)值大于等于1小于等于10
        t2.setPriority(Thread.MIN_PRIORITY);    // MIN_PRIORITY = 1
        t2.start();

        t3.setPriority(Thread.MAX_PRIORITY);   // MAX_PRIORITY = 10
        t3.start();

        t4.setPriority(4);
        t4.start();

        t5.setPriority(5);
        t5.start();

        t6.setPriority(6);
        t6.start();
    }
}

class MyPriority implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "----->" + Thread.currentThread().getPriority());
    }
}

7. 守護(hù)線程(daemon)

  • 線程分為 用戶(hù)線程守護(hù)線程
  • java虛擬機(jī)必須保證用戶(hù)線程執(zhí)行完畢(比如我們的main線程)
  • 虛擬機(jī)不用等待守護(hù)線程執(zhí)行完畢(后臺(tái)記錄操作日志馏予,監(jiān)控內(nèi)存,垃圾回收盔性。霞丧。。都是守護(hù)線程)
  • 程序停止了冕香,守護(hù)線程也就停止了
  • 調(diào)用線程對(duì)象的setDaemon(true)設(shè)置線程為守護(hù)線程
public class TestDaemon {

    public static void main(String[] args) {
        God god = new God();
        Thread gt = new Thread(god);
        gt.setDaemon(true);   // 將該線程設(shè)置為守護(hù)線程蛹尝,默認(rèn)為flase
        gt.start();

        new Thread(new You()).start();    // 啟動(dòng)一般線程
    }


}

class You implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("你開(kāi)心的活著后豫!" + i);
        }
        System.out.println("=======goodbye world!");
    }
}

class God implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("上帝守護(hù)著你!");
        }
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市突那,隨后出現(xiàn)的幾起案子挫酿,更是在濱河造成了極大的恐慌,老刑警劉巖愕难,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件早龟,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡务漩,警方通過(guò)查閱死者的電腦和手機(jī)拄衰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)饵骨,“玉大人翘悉,你說(shuō)我怎么就攤上這事【哟ィ” “怎么了妖混?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)轮洋。 經(jīng)常有香客問(wèn)我制市,道長(zhǎng),這世上最難降的妖魔是什么弊予? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任祥楣,我火速辦了婚禮,結(jié)果婚禮上汉柒,老公的妹妹穿的比我還像新娘误褪。我一直安慰自己,他們只是感情好碾褂,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布兽间。 她就那樣靜靜地躺著,像睡著了一般正塌。 火紅的嫁衣襯著肌膚如雪嘀略。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,842評(píng)論 1 290
  • 那天乓诽,我揣著相機(jī)與錄音帜羊,去河邊找鬼。 笑死鸠天,一個(gè)胖子當(dāng)著我的面吹牛逮壁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼窥淆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了巍杈?” 一聲冷哼從身側(cè)響起忧饭,我...
    開(kāi)封第一講書(shū)人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎筷畦,沒(méi)想到半個(gè)月后词裤,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鳖宾,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年吼砂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鼎文。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡渔肩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拇惋,到底是詐尸還是另有隱情周偎,我是刑警寧澤,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布撑帖,位于F島的核電站蓉坎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏胡嘿。R本人自食惡果不足惜蛉艾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望衷敌。 院中可真熱鬧勿侯,春花似錦、人聲如沸逢享。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)瞒爬。三九已至弓柱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間侧但,已是汗流浹背矢空。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留禀横,地道東北人屁药。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像柏锄,于是被迫代替她去往敵國(guó)和親酿箭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子复亏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349

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