溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java 線程淺析

發(fā)布時間:2020-07-06 15:25:35 來源:網(wǎng)絡(luò) 閱讀:475 作者:wx5c78c8b1dbb1b 欄目:編程語言

一、什么是線程
要理解什么線程,我么得先知道什么是進程。現(xiàn)代操作系統(tǒng)在運行一個程序時,會為其創(chuàng)建一個進程。例如啟動eclipse.exe其實就是啟動了win系統(tǒng)的一個進程?,F(xiàn)代操作系統(tǒng)調(diào)度的最小單元就是線程,也叫輕量級進程,在一個進程里面包含多個線程,這些線程都有各自的計數(shù)器、堆棧等,并且能夠共享內(nèi)存變量。例如我們啟動了一個eclipse進程,我們運行在其中的程序就可以理解為線程。
二、為什么要使用線程
(1)更多的處理器核心(可以運行更多的線程)。
(2)更快的響應(yīng)時間(線程可以并行執(zhí)行)。
(3)更好的編程模型。
三、線程的狀態(tài)
Java線程在運行的生命周期中有6中不同的狀態(tài),在給定的一個時刻,線程只能處于其中一個狀態(tài)。如下圖所示。

狀態(tài)名稱 說明
NEW 初始狀態(tài),線程被創(chuàng)建,但是還沒有調(diào)用start方法。
RUNNABLE 運行狀態(tài),Java線程將操作系統(tǒng)中的就緒和運行兩種狀態(tài)統(tǒng)稱地稱作“運行中”
BLOCKED 阻塞狀態(tài),表示線程阻塞于鎖
WAITING 等待狀態(tài),表示線程進入等待狀態(tài),進入該狀態(tài)表示當(dāng)前線程需要等待其他線程做出一些特定動作(通知或中斷)
TIME_WAITING 超時狀態(tài),該狀態(tài)不同于WAITING,它是可以在指定時間自行返回的
TERMINATED 停止?fàn)顟B(tài),表示當(dāng)前線程已經(jīng)執(zhí)行完畢

四、線程的調(diào)度(狀態(tài)的變化)
我們先來看一張圖:線程的調(diào)度對線程狀態(tài)的影響
Java 線程淺析
1)NEW(狀態(tài))線程創(chuàng)建未啟動時的狀態(tài)。如下代碼示例:

Thread thread = new Thread(new ThreadTest());
        System.out.println(thread.getState());

輸出結(jié)果:
NEW

Process finished with exit code 0
2)NEW-RUNNABLE線程調(diào)用start方法。如下代碼示例:

Thread thread = new Thread(new ThreadTest());
        thread.start();
        System.out.println(thread.getState());

輸出結(jié)果:
RUNNABLE
線程調(diào)用yield()方法,yield方法的作用就是讓出CPU,當(dāng)前線程從運行中變?yōu)榭蛇\行狀態(tài)(READY),讓和它同級或者更高級別的線程運行,但是不能保證運行的線程立馬變成可運行狀態(tài)(不確定的)??慈缦麓a示例:
代碼設(shè)置了線程的優(yōu)先級,但是測試了幾次的測試結(jié)果都不相同。

**
 * 測試yield方法
 */
public class ThreadYieldTest {

    public static void main (String[] args) {
        Thread threadone = new Thread(new ThreadTestOne(),"ThreadTestOne");
        Thread threadtwo = new Thread(new ThreadTestTwo(),"ThreadTestTwo");
        threadone.setPriority(Thread.MIN_PRIORITY);
        threadtwo.setPriority(Thread.MAX_PRIORITY);
        threadone.start();
        threadtwo.start();
    }

    static class ThreadTestOne implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println("ThreadTestOne----MIN_PRIORITY-----"+i);
                Thread.yield();
            }
        }
    }

    static class ThreadTestTwo implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println("ThreadTestTwo-----MAX_PRIORITY----"+i);
                Thread.yield();
            }
        }
    }
}

結(jié)果一:
ThreadTestOne----MIN_PRIORITY-----0
ThreadTestTwo-----MAX_PRIORITY----0
ThreadTestOne----MIN_PRIORITY-----1
ThreadTestTwo-----MAX_PRIORITY----1
結(jié)果二:
ThreadTestTwo-----MAX_PRIORITY----0
ThreadTestOne----MIN_PRIORITY-----0
ThreadTestTwo-----MAX_PRIORITY----1
ThreadTestOne----MIN_PRIORITY-----1
ThreadTestTwo-----MAX_PRIORITY----2
ThreadTestTwo-----MAX_PRIORITY----3

3)RUNNABLE-WAITING(調(diào)用wait、join等方法)
(1)、wait方法可以讓一個線程的狀態(tài)變?yōu)閃AITING或者TIME_WAITING,wait方法的作用是讓當(dāng)前線程進入等待隊列,讓出CPU的執(zhí)行權(quán),線程的狀態(tài)變化為等待狀態(tài),執(zhí)行wait方法的前提就是獲取到對象鎖,因為執(zhí)行線程需要知道進入誰的等待隊列,之后才能被誰喚醒??慈缦麓a示例:(jps 看java的進程id,jstack 看java線程的信息)

static class Parent implements Runnable {

        @Override
        public void run() {
            synchronized (lock){
                System.out.println("執(zhí)行l(wèi)ock.wait");
                try {
                    lock.wait();
                    // 不會執(zhí)行
                    System.out.println(Thread.currentThread().getName()+"----------"+Thread.currentThread().getState());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

通過命令看下線程信息如下圖所示:可以看到Thread.State:WAITING
Java 線程淺析

(2)、join方法也可以使線程以讓一個線程的狀態(tài)變?yōu)閃AITING或者TIME_WAITING,join的方法的作用,字面意思加入、參加。我們可以這么理解join方法,一個線程加入一個正在運行的主線程中,并且使得正在運行的主線程等待加入的線程執(zhí)行完畢才能繼續(xù)執(zhí)行??慈缦麓a示例:
1、我們在main方法啟動兩個線程,分別調(diào)用jion方法和不調(diào)用,看下執(zhí)行結(jié)果一(不調(diào)用join方法):
Parent-----------------
Child-----------
結(jié)果順序不確定
Child-----------
Parent-----------------

public static void main (String[] args) {
        Thread thread = new Thread(new Parent());
        thread.setName("Parent");

        Thread threadChild = new Thread(new Child());
        threadChild.setName("Child");

        thread.start();
        threadChild.start();
    }

    /**
     * 父線程
     */
    static class Parent implements Runnable {

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"-----------------");
        }
    }

    /**
     * 子線程
     */
    static class Child  implements Runnable {

        @Override
        public void run() {
                System.out.println(Thread.currentThread().getName()+"-----------");
        }
    }

結(jié)果二(其中一個調(diào)用join方法)
(多次運行結(jié)果順序不變)
Parent-----------------
Child-----------

public static void main (String[] args) {
        Thread thread = new Thread(new Parent());
        thread.setName("Parent");

        Thread threadChild = new Thread(new Child());
        threadChild.setName("Child");

        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        threadChild.start();
    }

2、join方法源碼
可以看出join方法是調(diào)用的wait方法,所以線程的狀態(tài)會變成WAITING或者TIME_WAITING。
我們來分析下是哪個線程加入等待隊列,可以看出join方法是個synchronized方法,也就是說鎖對象就是調(diào)用者,然后擁有鎖對象的線程調(diào)用wait方法進入等待隊列。我們通過上面的main方法來分析到底是誰進入等待隊列,主角有main線程、Parent線程、Child線程,我們在main線程里面new了Parent線程和Child線程,然后在Child線程啟動前面調(diào)用了Parent線程的join方法,也就是說是Parent線程調(diào)用了join方法,所以鎖對象就是Parent線程實例,我們再來分析是那個線程擁有這個鎖對象,答案是main線程,所以main線程調(diào)用wait方法進入等待隊列Parent線程執(zhí)行完畢;join方法結(jié)束時會喚醒主線程。

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

4)RUNNABLE-TIME_WAITING(調(diào)用sleep、wait、join等方法)
sleep(long)方法就是讓線程睡眠一定的時間在執(zhí)行,不過這個是有時間限制的,到了時間就會又變成RUNNABLE狀態(tài)。如下代碼示例:

static class Parent implements Runnable {

        @Override
        public void run() {
            try {
                Thread.sleep(1000);
                System.out.println("-----------------------");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

結(jié)果
Java 線程淺析
join(long)和wait(long)方法和WAITING狀態(tài)的方式類似,只是加入時間后線程可以自動喚醒,自動從等待隊列加入同步隊列,獲取到鎖變成RUNNABLE狀態(tài)。
sleep和wait的區(qū)別:
相同點:sleep和wait都可以使線程等待特定的時間;都可以使用interrupt()后調(diào)用阻塞方法中斷線程;
不同點:sleep是Thread方法,wait是Object的方法;slepp到了時間自動喚醒,而wait沒有規(guī)定時間時需要手動喚醒;在synchronized關(guān)鍵字修飾的方法或者塊中,sleep不會釋放鎖,wait會釋放鎖。
4)TIME_WAITING or WAITING-RUNNABLE(調(diào)用notify()、notifyAll(),sleep時間到了)
sleep時間到了線程就會進入RUNNABLE狀態(tài)(但是可能是RUNNING or READY狀態(tài))。
notify()是喚醒一個線程,進入同步隊列,沒有獲取到鎖就是BLOCKED狀態(tài),獲取到鎖就是RUNNABLE狀態(tài)。
notifyAll()是喚醒等待隊列的所有線程進入同步隊列。
5)RUNNABLE-BLOCKED(線程獲取鎖失敗,進入同步隊列)
如下代碼示例:

static class Parent implements Runnable {

        @Override
        public void run() {
            synchronized (lock) {
                    System.out.println(Thread.currentThread().getName()+"----------------------------------");
                try {
                    Thread.sleep(100000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 子線程
     */
    static class Child  implements Runnable {

        @Override
        public void run() {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName()+"----------------------------------");
            }
        }
    }

結(jié)果Java 線程淺析
6)RUNNABLE-TERMINATED(線程執(zhí)行完畢)
五、如何優(yōu)雅終止線程(手動)
我們知道線程提供了interrupt()、stop()方法;中斷線程的三種方式;
1)stop方法,停止一個線程,現(xiàn)在已經(jīng)是一個過期方法(不推薦使用)。
代碼示例:

public static void main (String[] args) throws InterruptedException {
        Thread thread = new Thread(new Parent(),"Parent");
        thread.start();
        Thread.sleep(200);
        thread.stop();
    }

    /**
     * 父線程
     */
    static class Parent implements Runnable {

        @Override
        public void run() {
            while (true) {
                System.out.println("-----------------------");
            }
        }
    }

2) 使用interrupt()方法,只是給線程設(shè)置了一個中斷標(biāo)記,并不會中斷線程,配合阻塞方法才能實現(xiàn)線程的中斷。
如下代碼示例:
中斷了
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.ge.thread.method.ThreadStopTest$Parent.run(ThreadStopTest.java:30)
at java.lang.Thread.run(Thread.java:745)

public static void main (String[] args)  {
        Thread thread = new Thread(new Parent(),"Parent");
        thread.start();
        thread.interrupt();
    }

    /**
     * 父線程
     */
    static class Parent implements Runnable {

        @Override
        public void run() {
            try {
                while (true) {
                    /*System.out.println("------------");
                    // 測試 isInterrupted方法
                    System.out.println("0isInterrupted------"+Thread.currentThread().isInterrupted());
                    // 測試interrupted方法
                    System.out.println("1interrupted------"+  Thread.interrupted());
                    System.out.println("2interrupted------"+  Thread.interrupted());*/
                    Thread.sleep(500);

                }
            }catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("中斷了");
            }
        }
    }

interrupt、interrupted和isInterrupted方法的區(qū)別,大家可以運行上面注釋代碼看運行結(jié)果。
interrupt:給線程一個中斷標(biāo)記,配合阻塞方法中斷線程,拋出InterruptedException異常,并清除標(biāo)記。
interrupted:Thread的靜態(tài)方法,返回線程的中斷標(biāo)記狀態(tài),并且清理,所以第一次返回true或者false,第二次一定是false。
isInterrupted:返回線程的中斷標(biāo)記狀態(tài)。
3)給線程的執(zhí)行設(shè)置一個標(biāo)記,滿足就執(zhí)行,不滿足就結(jié)束線程。
如下代碼示例:

 private static volatile boolean flag = true;

    public static void main (String[] args) {
        Thread thread = new Thread(new Parent());
        thread.setName("Parent");
        thread.start();
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 中斷線程標(biāo)記
        flag = false;
    }

    /**
     * 父線程
     */
    static class Parent implements Runnable {

        @Override
        public void run() {
            while (flag && !Thread.currentThread().isInterrupted()) {
                System.out.println("--------------");
            }
        }
    }

4)總結(jié),stop方法就是強行結(jié)束線程,不推薦可能造成業(yè)務(wù)數(shù)據(jù)未知的錯誤,因為線程運行在那個過程時未知的;interrupt通過標(biāo)記和阻塞方法一起中斷線程,會拋出異常,但是要對異常做處理,例如處理線程為執(zhí)行完成的任務(wù)或者回滾保證業(yè)務(wù)正確;推薦標(biāo)記法,因為線程會走一個完整的過程,不會出現(xiàn)業(yè)務(wù)方面的未知錯誤,線程要么執(zhí)行,要么不執(zhí)行,不會執(zhí)行一半就退出,所以就不會出現(xiàn)未知錯誤。
六、總結(jié)
本文針對線程的生命周期,線程的每個狀態(tài)進行解釋,以及線程執(zhí)行過程的狀態(tài)變化;線程調(diào)度對線程狀態(tài)的影響,以及一些線程的基本方法;最后介紹了停止線程的三種方式;通過學(xué)習(xí)這些有助于我們理解線程的基礎(chǔ),為學(xué)習(xí)多線程打下基礎(chǔ),只有學(xué)習(xí)好了單線程,才能更好的學(xué)習(xí)多線程;希望與諸君共勉。

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI