多線程——線程通訊

前言

  今天小鹹兒來講解一個好玩的事,那就是線程之間該如何通信,線程通信之後又會出現什麼問題?


敘述

寶圖

  先來一張導圖來看看線程通訊的分佈?
在這裏插入圖片描述

Join 線程

  疑問:如果想要線程按照用戶自定義的順序執行的話,那該如何操作呢?

  思考:如果能夠讓線程等待先執行的線程執行完,再執行不就能達到效果了嗎!?

  果然出現問題之後,就會有對應的解決之法,接下來先看一個簡單而且有趣的方法——join

Thread 提供了讓一個線程等待另一個線程完成的方法——join()方法。

代碼展示:

package com.practice.demo.thread;

/**
 * 線程的第三個實例,繼承Thread類
 * 測試join()方法
 * @author Phyllis
 * @date 2019年7月2日17:26:23
 */
public class JoinThread extends Thread{
    /**
     * 提供一個有參數的構造器,用於設置該線程的名字
     * @param name 線程的名字
     */
    public JoinThread(String name){
        super(name);
    }

    /**
     * 重寫run()方法,定義線程執行體
     */
    @Override
    public void run() {
        for (int i = 0; i<100; i++){
            System.out.println(getName()+"   "+ i);
        }
    }

    public static void main(String[] args) throws Exception{
        // 啓動子線程
        new JoinThread("新線程").start();
        for (int i = 0; i<100; i++){
            if (i == 20){
                JoinThread jt = new JoinThread("被Join的線程");
                jt.start();
                // main線程調用了jt線程的join()方法,main線程
                // 必須等待jt執行結束纔會向下執行
                jt.join();
            }
            System.out.println(Thread.currentThread().getName()+"    "+i);
        }
    }
}

打印結果:
在這裏插入圖片描述
在這裏插入圖片描述
  結果: 當i=20時,被join的線程開始執行,這時,main線程一直處於阻塞的狀態,所以一直在等待,等到join的線程結束後,則開始執行。

sleep

  這個方法看到名字就會很清楚是幹什麼用的了,就是表面意思,讓線程睡一會。

synchronized (res){
    try {
         if (!res.flag){
              res.wait();
         }
         Thread.sleep(1000);
     }catch (Exception e){

     }
     System.out.println(res.name + "," + res.sex);
     res.flag = false;
     res.notify();
}

如果需要讓當前正在執行的線程暫停一段時間,並進入阻塞狀態,則可以通過Thread類的靜態sleep()方法來實現。

  與之類似的方法還有一個yield()方法

yield

  yield()靜態方法和sleep()方法類似,它也可以讓當前正在執行的線程暫停,但它不會阻塞該線程,它只是將該線程轉入就緒狀態。

synchronized (res){
    try {
         if (!res.flag){
              res.wait();
         }
         Thread.yield();
     }catch (Exception e){

     }
     System.out.println(res.name + "," + res.sex);
     res.flag = false;
     res.notify();
}

缺點: yield方法只是讓當前線程暫停一下,讓系統的線程調度器重新調度一次,完全可能的情況是:當某個線程調用了yield方法暫停之後,線程調度器又再次調度到了該線程。

  sleep方法和yield方法的區別:

  • sleep方法暫停當前線程後,會給其他線程執行的機會,不會理會其他線程的優先級;但是yield方法只會給優先級相同或者更高級別的線程機會。(看來yield還看高低區分的哼)
  • sleep方法會將線程轉入阻塞狀態,直到經過阻塞時間纔會轉入就緒狀態;而yield不會將線程轉入阻塞狀態,它只是強制當前線程進入就緒狀態。因此有可能在暫停後,因獲得處理器資源而再次執行。
  • sleep方法聲明拋出了InterruptException異常,所以調用sleep方法時要麼捕捉該異常,要不顯示聲明拋出該異常;而yield方法則沒有聲明拋出任何異常。
  • sleep方法比yield方法有更好的可移植性。

等待喚醒機制

  重點來了,等待喚醒機制是什麼,在多線程執行過程中,在A線程對共享變量,進行操作時,B線程要等待。A線程結束操作時,則喚醒B線程

在這裏插入圖片描述
示例:生產與消費

package com.practice.demo.thread;

/**
 * 共享對象
 * @author Phyllis
 * @date 2019年7月12日09:15:26
 */
class Res{
    /**
     * 姓名
     */
    public String name;
    /**
     * 性別
     */
    public String sex;
    /**
     * 爲true的情況下,允許讀,不能寫
     * 爲false的情況下,允許寫,不能讀
     */
    public boolean flag = false;
}
/**
 * 生產這線程
 * @author Phyllis
 * @date 2019年7月12日09:16:55
 */
class InThread extends Thread{
    public Res res;

    public InThread(Res res){
        this.res = res;
    }

    @Override
    public void run() {
        // count 爲0 或者 1
        int count = 0;
        while (true){
            synchronized (res){
                if (res.flag){
                    try {
                        // 釋放當前鎖對象,當前線程等待
                        res.wait();
                    }catch (Exception e){

                    }
                }
                if (count == 0){
                    res.name = "小紅";
                    res.sex = "女";
                } else{
                    res.name = "小軍";
                    res.sex = "男";
                }
                // 0 1 0 1 0 1 0 1
                count = (count + 1) % 2;
                // 標記當前線程爲等待
                res.flag = true;
                // 喚醒被等待的線程
                res.notify();
            }
        }
    }
}
/**
 * 消費這線程,讀
 * @author Phyllis
 * @date 2019年7月12日09:16:55
 */
class OutThread extends Thread {

    public Res res;

    public OutThread(Res res){
        this.res = res;
    }

    @Override
    public void run() {
        while (true){
            synchronized (res){
                try {
                    if (!res.flag){
                        res.wait();
                    }
                    Thread.sleep(1000);
                }catch (Exception e){

                }
                System.out.println(res.name + "," + res.sex);
                res.flag = false;
                res.notify();
            }
        }
    }
}
/**
 * 客戶端
 * @author Phyllis
 * @date 2019年7月12日09:16:55
 */
public class ThirdThread{

    public synchronized static void main(String[] args) throws InterruptedException {
        Res res = new Res();
        InThread inThread = new InThread(res);
        OutThread outThread = new OutThread(res);
        inThread.start();
        outThread.start();
    }
}

打印結果:
在這裏插入圖片描述
  所以使用等待喚醒機制來保證生產一個,消費一個。

注意: wait和notify方法,一定要在synchronized中進行,持有同一把鎖。

區別

  wait和join的區別:

  wait需要被喚醒。

  wait和sleep的區別:

  sleep不會釋放鎖。

問題

  在寶圖中也可以明顯的看出,小鹹兒在通訊那裏打了兩個問號,也就是說通訊有可能會導致線程安全的問題,那麼線程安全是什麼呢?又該如何解決呢?且聽小鹹兒下次分享。


總結

  多線程是十分實用並且常用的內容,接下來小鹹兒還會繼續深入學習多線程,更多的內容等待更新。

感謝您的閱讀~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章