溫馨提示×

溫馨提示×

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

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

Java多線程使用方式和實現(xiàn)原理

發(fā)布時間:2021-08-31 14:13:26 來源:億速云 閱讀:141 作者:chen 欄目:編程語言

本篇內(nèi)容介紹了“Java多線程使用方式和實現(xiàn)原理”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!

Java中的線程

Java之父對線程的定義是:

線程是一個獨立執(zhí)行的調(diào)用序列,同一個進程的線程在同一時刻共享一些系統(tǒng)資源(比如文件句柄等)也能訪問同一個進程所創(chuàng)建的對象資源(內(nèi)存資源)。java.lang.Thread對象負責(zé)統(tǒng)計和控制這種行為。

每個程序都至少擁有一個線程-即作為Java虛擬機(JVM)啟動參數(shù)運行在主類main方法的線程。在Java虛擬機初始化過程中也可能啟動其他的后臺線程。這種線程的數(shù)目和種類因JVM的實現(xiàn)而異。然而所有用戶級線程都是顯式被構(gòu)造并在主線程或者是其他用戶線程中被啟動。

  本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等。在這之前,首先讓我們來了解下在操作系統(tǒng)中進程和線程的區(qū)別:
  進程:每個進程都有獨立的代碼和數(shù)據(jù)空間(進程上下文),進程間的切換會有較大的開銷,一個進程包含1--n個線程。(進程是資源分配的最小單位)
  線程:同一類線程共享代碼和數(shù)據(jù)空間,每個線程有獨立的運行棧和程序計數(shù)器(PC),線程切換開銷小。(線程是cpu調(diào)度的最小單位)
  線程和進程一樣分為五個階段:創(chuàng)建、就緒、運行、阻塞、終止。
  多進程是指操作系統(tǒng)能同時運行多個任務(wù)(程序)。
  多線程是指在同一程序中有多個順序流在執(zhí)行。
在java中要想實現(xiàn)多線程,有兩種手段,一種是繼續(xù)Thread類,另外一種是實現(xiàn)Runable接口.(其實準確來講,應(yīng)該有三種,還有一種是實現(xiàn)Callable接口,并與Future、線程池結(jié)合使用

Java線程狀態(tài)機

Java 給多線程編程提供了內(nèi)置的支持。 一條線程指的是進程中一個單一順序的控制流,一個進程中可以并發(fā)多個線程,每條線程并行執(zhí)行不同的任務(wù)。

多線程是多任務(wù)的一種特別的形式,但多線程使用了更小的資源開銷。

這里定義和線程相關(guān)的另一個術(shù)語 - 進程:一個進程包括由操作系統(tǒng)分配的內(nèi)存空間,包含一個或多個線程。一個線程不能獨立的存在,它必須是進程的一部分。一個進程一直運行,直到所有的非守護線程都結(jié)束運行后才能結(jié)束。

多線程能滿足程序員編寫高效率的程序來達到充分利用 CPU 的目的。


一個線程的生命周期

線程是一個動態(tài)執(zhí)行的過程,它也有一個從產(chǎn)生到死亡的過程。

下圖顯示了一個線程完整的生命周期。

Java多線程使用方式和實現(xiàn)原理

  • 新建狀態(tài):

    使用 new 關(guān)鍵字和 Thread 類或其子類建立一個線程對象后,該線程對象就處于新建狀態(tài)。它保持這個狀態(tài)直到程序 start() 這個線程。

  • 就緒狀態(tài):

    當(dāng)線程對象調(diào)用了start()方法之后,該線程就進入就緒狀態(tài)。就緒狀態(tài)的線程處于就緒隊列中,要等待JVM里線程調(diào)度器的調(diào)度。

  • 運行狀態(tài):

    如果就緒狀態(tài)的線程獲取 CPU 資源,就可以執(zhí)行 run(),此時線程便處于運行狀態(tài)。處于運行狀態(tài)的線程最為復(fù)雜,它可以變?yōu)樽枞麪顟B(tài)、就緒狀態(tài)和死亡狀態(tài)。

  • 阻塞狀態(tài):

    如果一個線程執(zhí)行了sleep(睡眠)、suspend(掛起)等方法,失去所占用資源之后,該線程就從運行狀態(tài)進入阻塞狀態(tài)。在睡眠時間已到或獲得設(shè)備資源后可以重新進入就緒狀態(tài)??梢苑譃槿N:

    • 等待阻塞:運行狀態(tài)中的線程執(zhí)行 wait() 方法,使線程進入到等待阻塞狀態(tài)。

    • 同步阻塞:線程在獲取 synchronized 同步鎖失敗(因為同步鎖被其他線程占用)。

    • 其他阻塞:通過調(diào)用線程的 sleep() 或 join() 發(fā)出了 I/O 請求時,線程就會進入到阻塞狀態(tài)。當(dāng)sleep() 狀態(tài)超時,join() 等待線程終止或超時,或者 I/O 處理完畢,線程重新轉(zhuǎn)入就緒狀態(tài)。

  • 死亡狀態(tài):

    一個運行狀態(tài)的線程完成任務(wù)或者其他終止條件發(fā)生時,該線程就切換到終止狀態(tài)。

    Java多線程實戰(zhàn)

    多線程的實現(xiàn)

    public class 多線程實例 {

    //繼承thread
    @Test
    public void test1() {
        class A extends Thread {
            
    @Override
            public void run() {
                System.out.println("A run");
            }
        }
        A a = new A();
        a.start();
    }
    //實現(xiàn)Runnable
    @Test
    public void test2() {
        class B implements Runnable {
            
    @Override
            public void run() {
                System.out.println("B run");
            }
        }
        B b = new B();
        //Runable實現(xiàn)類需要由Thread類包裝后才能執(zhí)行
        new Thread(b).start();
    }
    //有返回值的線程
    @Test
    public void test3() {
        Callable callable = new Callable() {
            int sum = 0;
            
    @Override
            public Object call() throws Exception {
                for (int i = 0;i < 5;i ++) {
                    sum += i;
                }
                return sum;
            }
        };
        //這里要用FutureTask,否則不能加入Thread構(gòu)造方法
        FutureTask futureTask = new FutureTask(callable);
        new Thread(futureTask).start();
        try {
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
    //線程池實現(xiàn)
    @Test
    public void test4() {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        //execute直接執(zhí)行線程
        executorService.execute(new Thread());
        executorService.execute(new Runnable() {
            
    @Override
            public void run() {
                System.out.println("runnable");
            }
        });
        //submit提交有返回結(jié)果的任務(wù),運行完后返回結(jié)果。
        Future future = executorService.submit(new Callable<String>() {
            
    @Override
            public String call() throws Exception {
                return "a";
            }
        });
        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        ArrayList<String> list = new ArrayList<>();
        //有返回值的線程組將返回值存進集合
        for (int i = 0;i < 5;i ++ ) {
            int finalI = i;
            Future future1 = executorService.submit(new Callable<String>() {
                
    @Override
                public String call() throws Exception {
                    return "res" + finalI;
                }
            });
            try {
                list.add((String) future1.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        for (String s : list) {
            System.out.println(s);
        }
    }

    }

線程狀態(tài)轉(zhuǎn)換

public class 線程的狀態(tài)轉(zhuǎn)換 {
//一開始線程是init狀態(tài),結(jié)束時是terminated狀態(tài)
class t implements Runnable {
    private String name;
    public t(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        System.out.println(name + "run");
    }
}
//測試join,父線程在子線程運行時進入waiting狀態(tài)
@Test
public void test1() throws InterruptedException {
    Thread dad = new Thread(new Runnable() {
        Thread son = new Thread(new t("son"));
        @Override
        public void run() {
            System.out.println("dad init");
            son.start();
            try {
                //保證子線程運行完再運行父線程
                son.join();
                System.out.println("dad run");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
    //調(diào)用start,線程進入runnable狀態(tài),等待系統(tǒng)調(diào)度
    dad.start();
    //在父線程中對子線程實例使用join,保證子線程在父線程之前執(zhí)行完
}
//測試sleep
@Test
public void test2(){
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("t1 run");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
    //主線程休眠。進入time waiting狀態(tài)
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    t1.start();
}
//線程2進入blocked狀態(tài)。
public static void main(String[] args) {
    test4();
    Thread.yield();//進入runnable狀態(tài)
}
//測試blocked狀態(tài)
public static void test4() {
    class A {
        //線程1獲得實例鎖以后線程2無法獲得實例鎖,所以進入blocked狀態(tài)
        synchronized void run() {
            while (true) {
                System.out.println("run");
            }
        }
    }
    A a = new A();
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("t1 get lock");
            a.run();
        }
    }).start();
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("t2 get lock");
            a.run();
        }
    }).start();
}
//volatile保證線程可見性
volatile static int flag = 1;
//object作為鎖對象,用于線程使用wait和notify方法
volatile static Object o = new Object();
//測試wait和notify
//wait后進入waiting狀態(tài),被notify進入blocked(阻塞等待鎖釋放)或者runnable狀態(tài)(獲取到鎖)
public void test5() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            //wait和notify只能在同步代碼塊內(nèi)使用
            synchronized (o) {
                while (true) {
                    if (flag == 0) {
                        try {
                            Thread.sleep(2000);
                            System.out.println("thread1 wait");
                            //釋放鎖,線程掛起進入object的等待隊列,后續(xù)代碼運行
                            o.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("thread1 run");
                    System.out.println("notify t2");
                    flag = 0;
                    //通知等待隊列的一個線程獲取鎖
                    o.notify();
                }
            }
        }
    }).start();
    //解釋同上
    new Thread(new Runnable() {
        @Override
        public void run() {
            while (true) {
                synchronized (o) {
                    if (flag == 1) {
                        try {
                            Thread.sleep(2000);
                            System.out.println("thread2 wait");
                            o.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("thread2 run");
                    System.out.println("notify t1");
                    flag = 1;
                    o.notify();
                }
            }
        }
    }).start();
}
//輸出結(jié)果是
//    thread1 run
//    notify t2
//    thread1 wait
//    thread2 run
//    notify t1
//    thread2 wait
//    thread1 run
//    notify t2
//不斷循環(huán)
}

Java Thread常用方法

Thread#yield():

執(zhí)行此方法會向系統(tǒng)線程調(diào)度器(Schelduler)發(fā)出一個暗示,告訴其當(dāng)前JAVA線程打算放棄對CPU的使用,但該暗示,有可能被調(diào)度器忽略。使用該方法,可以防止線程對CPU的過度使用,提高系統(tǒng)性能。

Thread#sleep(time)或Thread.sleep(time, nanos):

使當(dāng)前線程進入休眠階段,狀態(tài)變?yōu)椋篢IME_WAITING

Thread.interrupt():

中斷當(dāng)前線程的執(zhí)行,允許當(dāng)前線程對自身進行中斷,否則將會校驗調(diào)用方線程是否有對該線程的權(quán)限。

如果當(dāng)前線程因被調(diào)用Object#wait(),Object#wait(long, int), 或者線程本身的join(), join(long),sleep()處于阻塞狀態(tài)中,此時調(diào)用interrupt方法會使拋出InterruptedException,而且線程的阻塞狀態(tài)將會被清除。

Thread#interrupted(),返回true或者false:

查看當(dāng)前線程是否處于中斷狀態(tài),這個方法比較特殊之處在于,如果調(diào)用成功,會將當(dāng)前線程的interrupt status清除。所以如果連續(xù)2次調(diào)用該方法,第二次將返回false。

Thread.isInterrupted(),返回true或者false:

與上面方法相同的地方在于,該方法返回當(dāng)前線程的中斷狀態(tài)。不同的地方在于,它不會清除當(dāng)前線程的interrupt status狀態(tài)。

Thread#join(),Thread#join(time):

A線程調(diào)用B線程的join()方法,將會使A等待B執(zhí)行,直到B線程終止。如果傳入time參數(shù),將會使A等待B執(zhí)行time的時間,如果time時間到達,將會切換進A線程,繼續(xù)執(zhí)行A線程。

構(gòu)造方法和守護線程

構(gòu)造方法
Thread類中不同的構(gòu)造方法接受如下參數(shù)的不同組合:
一個Runnable對象,這種情況下,Thread.start方法將會調(diào)用對應(yīng)Runnable對象的run方法。如果沒有提供Runnable對象,那么就會立即得到一個Thread.run的默認實現(xiàn)。
一個作為線程標識名的String字符串,該標識在跟蹤和調(diào)試過程中會非常有用,除此別無它用。
線程組(ThreadGroup),用來放置新創(chuàng)建的線程,如果提供的ThreadGroup不允許被訪問,那么就會拋出一個SecurityException 。
Thread對象擁有一個守護(daemon)標識屬性,這個屬性無法在構(gòu)造方法中被賦值,但是可以在線程啟動之前設(shè)置該屬性(通過setDaemon方法)。
當(dāng)程序中所有的非守護線程都已經(jīng)終止,調(diào)用setDaemon方法可能會導(dǎo)致虛擬機粗暴的終止線程并退出。
isDaemon方法能夠返回該屬性的值。守護狀態(tài)的作用非常有限,即使是后臺線程在程序退出的時候也經(jīng)常需要做一些清理工作。
(daemon的發(fā)音為”day-mon”,這是系統(tǒng)編程傳統(tǒng)的遺留,系統(tǒng)守護進程是一個持續(xù)運行的進程,比如打印機隊列管理,它總是在系統(tǒng)中運行。)

啟動線程的方式和isAlive方法

啟動線程
調(diào)用start方法會觸發(fā)Thread實例以一個新的線程啟動其run方法。新線程不會持有調(diào)用線程的任何同步鎖。

當(dāng)一個線程正常地運行結(jié)束或者拋出某種未檢測的異常(比如,運行時異常(RuntimeException),錯誤(ERROR) 或者其子類)線程就會終止。

當(dāng)線程終止之后,是不能被重新啟動的。在同一個Thread上調(diào)用多次start方法會拋出InvalidThreadStateException異常。

如果線程已經(jīng)啟動但是還沒有終止,那么調(diào)用isAlive方法就會返回true.即使線程由于某些原因處于阻塞(Blocked)狀態(tài)該方法依然返回true。

如果線程已經(jīng)被取消(cancelled),那么調(diào)用其isAlive在什么時候返回false就因各Java虛擬機的實現(xiàn)而異了。沒有方法可以得知一個處于非活動狀態(tài)的線程是否已經(jīng)被啟動過了。

Java多線程優(yōu)先級

Java的線程實現(xiàn)基本上都是內(nèi)核級線程的實現(xiàn),所以Java線程的具體執(zhí)行還取決于操作系統(tǒng)的特性。

Java虛擬機為了實現(xiàn)跨平臺(不同的硬件平臺和各種操作系統(tǒng))的特性,Java語言在線程調(diào)度與調(diào)度公平性上未作出任何的承諾,甚至都不會嚴格保證線程會被執(zhí)行。但是Java線程卻支持優(yōu)先級的方法,這些方法會影響線程的調(diào)度:

每個線程都有一個優(yōu)先級,分布在Thread.MIN_PRIORITY和Thread.MAX_PRIORITY之間(分別為1和10)
默認情況下,新創(chuàng)建的線程都擁有和創(chuàng)建它的線程相同的優(yōu)先級。main方法所關(guān)聯(lián)的初始化線程擁有一個默認的優(yōu)先級,這個優(yōu)先級是Thread.NORM_PRIORITY (5).

線程的當(dāng)前優(yōu)先級可以通過getPriority方法獲得。
線程的優(yōu)先級可以通過setPriority方法來動態(tài)的修改,一個線程的最高優(yōu)先級由其所在的線程組限定。

“Java多線程使用方式和實現(xiàn)原理”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

向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