中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

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

Java多線程Thread類如何使用

發布時間:2022-06-20 15:13:09 來源:億速云 閱讀:144 作者:iii 欄目:開發技術

這篇文章主要講解了“Java多線程Thread類如何使用”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Java多線程Thread類如何使用”吧!

    Thread類的基本用法

    1.創建子類,繼承自Thread并且重寫run方法:

    class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("hello thread");
        }
    }
    public class Demo1 {
        public static void main(String[] args) {
            // 最基本的創建線程的辦法.
            Thread t = new MyThread();
            //調用了start方法才是真正的在系統中創建了線程,執行run方法
            t.start();
        }
    }

    2.創建一個類,實現Runnable接口再創建Runnable是實例傳給Thread

    class MyRunnable implements Runnable{
        @Override
        public void run() {
            System.out.println("hello");
        }
    }
    public class Demo3 {
        public static void main(String[] args) {
            Thread t = new Thread(new MyRunnable());
            t.start();
        }
    }

    3.匿名內部類

    創建了一個匿名內部類,繼承自Thread類,同時重寫run方法,再new出匿名內部類的實例

    public class Demo4 {
        public static void main(String[] args) {
            Thread t = new Thread(){
                @Override
                public void run() {
                    System.out.println("hello");
                }
            };
            t.start();
        }
    }

    new的Runnable,針對這個創建的匿名內部類,同時new出的Runnable實例傳給Thread的構造方法

    public class Demo5 {
        public static void main(String[] args) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello");
                }
            });
            t.start();
        }
    }

    4.lambda表達式 lambda代替Runnable

    public class Demo6 {
        public static void main(String[] args) {
            Thread t = new Thread(() ->{
                System.out.println("hello");
            });
            t.start();
        }
    }

    線程指標

    1.isDaemon();
    是否后臺線程 后臺線程不影響進程退出,不是后臺線程會影響進程退出

    2.isAlive();
    是否存活 在調用start前系統中是沒有對應線程的,run方法執行完后線程就銷毀了,t對象可能還存在

    3.isinterrupted();
    是否被中斷

    run和start的區別

    run單純的只是一個普通方法描述了任務的內容 start則是一個特殊的方法,內部會在系統中創建線程

    中斷線程

    線程停下來的關鍵是要讓對應run方法執行完,對于main線程來說main方法執行完了才會終止

    1.手動設置標志位
    在線程中控制這個標志位就能影響到這個線程結束,但是此處多個線程共用一片虛擬空間,因此main線程修改的isQuit和t線程判斷的isQuit是同一個值

    public class Demo10 {
        // 通過這個變量來控制線程是否結束.
        private static boolean isQuit = false;
        public static void main(String[] args) {
            Thread t = new Thread(() -> {
                while (!isQuit) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
    
            // 就可以在 main 線程中通過修改 isQuit 的值, 來影響到線程是否退出
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // main 線程在 5s 之后, 修改 isQuit 的狀態.
            isQuit = true;
        }
    }

    2.使用Thread中內置的一個標志位來判定 Thread.interruted()這是一個靜態方法 Thread.currentThread().isInterrupted()這是一個實例方法,其中currentThread能夠獲取到當前線程的實例

    public class Demo7 {
        public static void main(String[] args)  {
            Thread t = new Thread(() -> {
                while(!Thread.currentThread().isInterrupted()){
                    System.out.println("hello");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        // 當觸發異常之后, 立即就退出循環~
                        System.out.println("這是收尾工作");
                        break;
                    }
                }
            });
            t.start();
            try{
                Thread.sleep(5000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            // 在主線程中, 調用 interrupt 方法, 來中斷這個線程.
            // t.interrupt 的意思就是讓 t 線程被中斷!!
            t.interrupt();
        }
    }

    需要注意的是調用這個方法t.interrupt()可能會產生兩種情況:
    1)如果t線程處在就緒就設置線程的標志位為true
    2)如果t線程處在阻塞狀態(sleep),就會觸發一個InterruptExeception

    線程等待

    多個線程之間調度順序是不確定的,有時候我們需要控制線程之間的順序,線程等待就是一種控制線程執行順序的手段,此處的線程等待只要是控制線程結束的先后順序。
    哪個線程中的join,哪個線程就會阻塞等待直到對應的線程執行完畢為止。

    t.join();
    調用這個方法的線程是main線程,針對t這個對象調用的此時就是讓main等待t。
    代碼執行到join這一行就停下了,讓t先結束然后main繼續。

    t.join(10000);
    join提供了另一個版本為帶一個參數的,參數為等待時間10s之后join直接返回不再等待

    Thread.currentThread()

    能夠獲取當前線程的應用,哪個線程調用的currentThread就獲取到哪個線程的實例 對比this如下:
    對于這個代碼來說,通過繼承Thread的方法來創建線程。此時run方法中直接通過this拿到的就是當前Thread的實例

    public class Demo4 {
        public static void main(String[] args) {
            Thread t = new Thread(){
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                    System.out.println(this.getName());
                }
            };
            t.start();
        }
    }

    然而此處this不是指向Thread類型,而是指向Runnable,Runnable只是一個單純的任務沒有name屬性,要想拿到線程名字只能通過Thread.currentThread()

    public class Demo5 {
        public static void main(String[] args) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    //err
                    //System.out.println(this.getName());
                    //right
                    System.out.println(Thread.currentThread().getName());
                }
            });
            t.start();
        }
    }

    進程狀態

    針對系統層面:

    • 就緒

    • 阻塞

    java中Thread類進一步細化:

    • NEW:把Thread對象創建好了但是還沒有調用start

    • TERMINATED:操作系統中的線程已執行完畢銷毀,但是Thread對象還在獲取到的狀態

    • RUNNABLE:就緒狀態,處在該狀態的線程就是在就緒隊列中,隨時可以調度到CPU上

    • TIME_WAITING:調用了sleep就會進入到該狀態,join(超時時間) BLOCKED:當前線程在等待鎖導致了阻塞

    • WAITING:當前線程在等待喚醒

    狀態轉換圖:

    Java多線程Thread類如何使用

    線程安全問題

    定義:操作系統中線程調度是隨機的,導致程序的執行可能會出現一些bug。如果因為調度隨機性引入了bug線程就是不安全的,反之則是安全的。
    解決方法:加鎖,給方法直接加上synchronized關鍵字,此時進入方法就會自動加鎖,離開方法就會自動解鎖。當一個線程加鎖成功的時候,其他線程嘗試加鎖就會觸發阻塞等待,阻塞會一直持續到占用鎖的線程把鎖釋放為止。

    synchronized public void increase() {
            count++;
    }

    線程不安全產生的原因:

    • 1.線程是搶占式執行,線程間的調度充滿隨機性。

    • 2.多個線程對同一個變量進行修改操作

    • 3.針對變量的操作不是原子的

    • 4.內存可見性也會影響線程安全(針對同一個變量t1線程循環進行多次讀操作,t2線程少次修改操作,t1就不會從內存讀數據了而是從寄存器里讀)

    • 5.指令重排序,也是編譯器優化的一種操作,保證邏輯不變的情況下調整順序,解決方法synchronized。

    內存可見性解決方法:

    • 1.使用synchronized關鍵字 使用synchronized不光能保證指令的原子性,同時也能保證內存的可見性。被synchronized包裹起來的代碼編譯器就不會從寄存器里讀。

    • 2.使用volatile關鍵字 能夠保證內存可見性,禁止編譯器作出上述優化,編譯器每次執行判定相等都會重新從內存讀取。

    synchronized用法

    在java中每個類都是繼承自Object,每個new出來的實例里面一方面包含自己安排的屬性,另一方面包含了“對象頭”即對象的一些元數據。加鎖操作就是在這個對象頭里面設置一個標志位。

    1.直接修飾普通的方法

    使用synchronized的時候本質上是對某個“對象”進行加鎖,此時的鎖對象就是this。加鎖操作就是在設置this的對象頭的標志位,當兩個線程同時嘗試對同一個對象加鎖的時候才有競爭,如果是兩個線程在針對兩個不同對象加鎖就沒有競爭。

    class Counter{
    	public int count;
    	synchronized public void increase(){
    	count++;
    	}
    }

    2.修飾一個代碼塊

    需要顯示制定針對那個對象加鎖(java中的任意對象都可以作為鎖對象)

    public void increase(){
        synchronized(this){
        count++;
        }
    }

    3.修飾一個靜態方法

    相當于針對當前類的類對象加鎖,類對象就是運行程序的時候。class文件被加載到JVM內存中的模樣。

    synchronized public static void func(){
    }

    或者

    public static void func(){
        synchronized(Counter.class){
    
        }
    }

    監視器鎖monitor lock

    可重入鎖就是同一個線程針對同一個鎖,連續加鎖兩次,如果出現死鎖就是不可重入鎖,如果不會死鎖就是可重入的。因此就把synchronized實現為可重入鎖,下面的例子里啊連續加鎖操作不會導致死鎖。可重入鎖內部會記錄所被哪個線程占用也會記錄加鎖次數,因此后續再加鎖就不是真的加鎖而是單純地把技術給自增。

    synchronized public void increase(){
        synchronized(this){
            count++;
        }
    }

    死鎖的其他場景

    • 1.一個線程一把鎖

    • 2.兩個線程兩把鎖

    • 3.N個線程M把鎖(哲學家就餐問題,解決方法:先拿編號小的筷子)

    死鎖的四個必要條件(前三個都是鎖本身的特點)

    • 1.互斥使用,一個鎖被另一個線程占用后其他線程就用不了(鎖的本質,保證原子性)

    • 2.不可搶占,一個鎖被一個線程占用后其他線程不可把這個鎖給挖走

    • 3.請求和保持,當一個線程占據了多把鎖之后,除非顯示的釋放否則這些鎖中都是該線程持有的

    • 4.環路等待,等待關系成環(解決:遵循固定的順序加鎖就不會出現環路等待)

    java線程類:

    • 不安全的:ArrayList,LinkedList,HashMap,TreeMap,HashSet,TreeSet,StringBuilder

    • 安全的:Vector,HashTable,ConcurrentHashMap,StringBuffer,String

    volatile

    禁止編譯器優化保證內存可見性,產生原因:計算機想執行一些計算就需要把內存的數據讀到CPU寄存器中,然后再從寄存器中計算寫回到內存中,因為CPU訪問寄存器的速度比訪問內存快很多,當CPU連續多次訪問內存結果都一樣,CPU就會選擇訪問寄存器。

    JMM(Java Memory Model)Java內存模型

    就是把硬件結構在java中用專業的術語又重新抽象封裝了一遍。

    工作內存(work memory)其實指的不是內存,而是CPU寄存器。
    主內存(main memeory)這才是主內存。
    原因:java作為一個跨平臺編程語言要把硬件細節封裝起來,假設某個計算機沒有CPU或者內存同樣可以套到上述模型中。

    寄存器,緩存和內存之間的關系

    CPU從內存取數據太慢,因此把數據直接放到寄存器里來讀,但寄存器空間太緊張于是又搞了一個存儲空間,比寄存器大比內存小速度比寄存器慢比內存快稱為緩存。寄存器和緩存統稱為工作內存。

    寄存器,緩存和內存之間的關系圖

    Java多線程Thread類如何使用

    • 存儲空間:CPU<L1<L2<L3<內存

    • 速度:CPU>L1>L2>L3>內存

    • 成本:CPU>L1>L2>L3>內存

    volatile和synchronized的區別

    • volatile只是保證可見性不保證原子性,只是處理一個線程讀和一個線程寫的過程。

    • synchronized都能處理

    wait和notify

    等待和通知處理線程調度隨機性問題的,join也是一種控制順序的方式更傾向于控制線程結束。wait和notify都是Object對象的方法,調用wait方法的線程就會陷入阻塞,阻塞到有線程通過notify來通知。

    public class Demo9 {
        public static void main(String[] args) throws InterruptedException {
            Object object = new Object();
            System.out.println("wait前");
            object.wait();
            System.out.println("wait后");
        }
    }

    wait內部會做三件事;

    • 1.先釋放鎖

    • 2.等待其他線程的通知

    • 3.收到通知后重新獲得鎖并繼續往下執行

    因此想用wait/notify就得搭配synchronized

    public class Demo9 {
        public static void main(String[] args) throws InterruptedException {
            Object object = new Object();
            synchronized (object){
                System.out.println("wait前");
                object.wait();
                System.out.println("wait后");
            }
        }
    }

    注意:wait notify都是針對同一對象來操作的,例如現在有一個對象o,有10個線程都調用了o.wait,此時10個線程都是阻塞狀態。如果調用了o.notify就會把10個線程中的一個線程喚醒。而notifyAll就會把所有10個線程全都給喚醒,此時就會競爭鎖。

    感謝各位的閱讀,以上就是“Java多線程Thread類如何使用”的內容了,經過本文的學習后,相信大家對Java多線程Thread類如何使用這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    碌曲县| 印江| 桂东县| 天等县| 黎川县| 历史| 迁安市| 汉阴县| 桂东县| 樟树市| 星子县| 鄂温| 宝兴县| 油尖旺区| 文登市| 稻城县| 天镇县| 县级市| 江山市| 兖州市| 读书| 南和县| 盐城市| 七台河市| 株洲县| 广东省| 灌阳县| 通山县| 康马县| 安仁县| 星子县| 唐海县| 延川县| 界首市| 义乌市| 尼勒克县| 灵山县| 娄烦县| 绥宁县| 靖边县| 日照市|