1. 程式人生 > 其它 >Java多執行緒-04-執行緒協作

Java多執行緒-04-執行緒協作

目錄

一.執行緒簡介

二.執行緒建立

三.執行緒狀態

四.執行緒同步

五.執行緒協作

五.執行緒協作

生產者消費者問題

為什麼需要執行緒通訊:為了解決生產者和消費者問題


問題分析


解決執行緒通訊的方法


解決方式1:管程法


解決方式2:訊號燈法

通過一個標誌位來判斷


管程法

例子:

生產者:廚師製作炸雞
消費者:吃炸雞
緩衝區:取餐檯(容器,用來放炸雞)

如果不使用管程法,就只能讓廚師先做完100只炸雞,然後顧客吃掉100只炸雞,這顯然不合理

  • wait() 會讓當前持有物件鎖的執行緒等待並釋放鎖

​ A.wait() 會讓當前持有A物件鎖的執行緒等待並釋放鎖

  • notifyAll() 喚醒正在等待此物件鎖的所有執行緒

    ​ A.notifyAll() 喚醒正在等待A物件鎖的所有執行緒

我說:

1.誰來呼叫兩個方法?

​ 被消費者和生產者操作的物件,即緩衝區

2.生產者何時等待何時被喚醒?

​ 緩衝區滿時生產者自行等待

​ 生產者消費後喚醒生產者

3.消費者何時等待何時被喚醒?

​ 緩衝區空時消費者自行等待

​ 生產者生產後喚醒消費者

package 四執行緒協作;

//生產者和消費者問題:利用緩衝區解決(管程法)
//例子
//生產者:廚師製作炸雞
//消費者:吃炸雞
//緩衝區:取餐檯(容器,用來放炸雞)
public class MonitorMethod {
    public static void main(String[] args) {
        PC_Container container = new PC_Container();

        new Productor("|廚師|", container).start();
        new Consumer("||顧客||", container).start();
    }
}

//生產者執行緒:廚師製作炸雞
class Productor extends Thread{
    public PC_Container container;

    public Productor(String name, PC_Container container){
        super(name);
        this.container = container;
    }

    @Override
    public void run() {
        //一共製作100只炸雞
        for (int i = 1; i <= 100; i++) {
            //製作一隻炸雞並放到前臺
            container.push(new Chicken(i));
            System.out.println(this.getName() + "-->生產了第"+ i + "只炸雞");
        }
    }
}

//消費者執行緒:吃炸雞
class Consumer extends Thread{
    public PC_Container container;

    public Consumer(String name, PC_Container container){
        super(name);
        this.container = container;
    }

    @Override
    public void run() {
        //一共吃100只炸雞
        for (int i = 1; i <= 100; i++) {
            //從前臺取一隻炸雞
            Chicken chicken = container.pop();
            System.out.println(this.getName() + "-->消費了第"+ chicken.id + "只炸雞");
        }
    }
}

//炸雞
class Chicken {
    //炸雞編號
    public int id;

    public Chicken(int id){
        this.id = id;
    }
}

//緩衝區:取餐檯(容器,用來放炸雞)
//會被生產者和消費者修改(多個執行緒操作同一個物件)
class PC_Container {
    //容器大小:前臺最多能放10只炸雞
    public Chicken[] chickens = new Chicken[10];
    //容器計數器
    public int count = 0;

    //放入炸雞
    //該方法涉及修改PC_Container,使用同步方法(監視器是this),即鎖PC_Container
    public synchronized void push(Chicken chicken){
        //容器滿了-前臺已經放滿10只炸雞了
        if(count == chickens.length){
            //讓生產者等待
            try {
                //wait()會讓當前持有PC_Container物件鎖的執行緒等待並釋放鎖
                //持有鎖並且跑push方法的只能是生產者執行緒,所以這裡的this.wait()會讓生產者執行緒等待
                this.wait();

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //如果可以生產
        //這裡不需要寫else if,因為如果生產者處於等待,那麼下面的程式碼就不會執行了
        //沒有滿,丟入炸雞
        chickens[count] = chicken;
        count++;

        //現在前臺有炸雞了,需要喚醒消費者
        //notifyAll()喚醒正在等待此物件監視器鎖的所有執行緒
        //notifyAll()可以喚醒生產者和消費者,但此時生產者本來就是醒,所以旨在喚醒消費者來吃炸雞
        this.notifyAll();
    }

    //放入炸雞
    //該方法涉及修改PC_Container,使用同步方法(監視器是this),即鎖PC_Container
    public synchronized Chicken pop(){
        //容器空了-前臺沒有炸雞了
        if(count == 0){
            //讓消費者等待
            try {
                //跑pop方法的只能是消費者執行緒,所以這裡的this.wait()會讓消費者執行緒等待
                this.wait();

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //如果可以消費
        //取走炸雞
        count--;//注意這裡要先減,count下標指向的是陣列中下一個空白的位置,count-1才是最新放入的炸雞
        Chicken chicken = chickens[count];

        //因為消費者取走了炸雞所以現在前臺一定不是滿的
        //需要通知生產者生產
        this.notifyAll();

        return chicken;
    }
}

訊號燈法

例子

生產者:演員表演節目
消費者:觀眾收看節目

演員和生產者都操作同一個電視機

演員通過 電視機上的訊號燈 判斷自己的狀態

1.等待

2.表演節目,之後提醒觀眾觀看

觀眾通過 電視機上的訊號燈 判斷自己的狀態

1.等待

2.收看節目,之後提醒演員表演

package 四執行緒協作.生產者消費者問題;

import java.security.PublicKey;

//生產者和消費者問題:利用標誌位解決(訊號燈法)
//例子
//生產者:演員表演節目
//消費者:觀眾收看節目
public class SignalLight {
    public static void main(String[] args) {
        TV tv = new TV();

        new PLayer(tv).start();
        new Watcher(tv).start();
    }
}

//生產者:演員
class PLayer extends Thread{
    public TV tv;
    public PLayer(TV tv){
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if(i%2==0)      this.tv.play("地球脈動");
            else            this.tv.play("COSMOS");
        }
    }
}

//消費者:觀眾
class Watcher extends Thread{
    public TV tv;
    public Watcher(TV tv){
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            this.tv.watch();
        }
    }
}

//電視機TV
class TV {
    //節目名字
    private String programName;

    //訊號燈標誌位
    //演員表演中,觀眾等待 ture
    //觀眾收看中,演員等待 false
    private boolean flag = true;

    //演員表演節目
    public synchronized void play(String programName){
        if(!flag){
            try {
                //觀眾收看中 演員等待
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //觀眾等待中 演員表演
        this.programName = programName;
        System.out.println("演員表演了-->" + programName);

        //演員表演完了
        this.flag = !this.flag;//訊號燈反轉
        //通知喚醒觀眾觀看
        this.notifyAll();
    }

    //觀眾收看節目
    public synchronized void watch(){
        if(flag){
            try {
                //演員表演中 觀眾等待
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //演員等待中 觀眾收看
        System.out.println("觀眾收看了-->" + this.programName);

        //觀眾收看完了
        this.flag = !this.flag;//訊號燈反轉
        //通知喚醒演員表演
        this.notifyAll();
    }
}

執行緒池



public class Pool_Test {
    public static void main(String[] args) {
        //1.建立服務,建立執行緒池
        ExecutorService service = Executors.newFixedThreadPool(10);//引數為執行緒池大小

        //執行
        service.execute(new MyThead());
        service.execute(new MyThead());
        service.execute(new MyThead());
        service.execute(new MyThead());

        //2.關閉連線
        service.shutdown();
    }
}

class MyThead implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}