多線程 使用wait和notify進行線程交互

線程之間有交互通知的需求,考慮如下情況:
有兩個線程,處理同一個英雄。
一個加血,一個減血。
減血的線程,發現血量=1,就停止減血,直到加血的線程爲英雄加了血,纔可以繼續減血

不使用wait和notify的解決方式

故意設計減血線程頻率更高,蓋倫的血量遲早會到達1
減血線程中使用while循環判斷是否是1,如果是1就不停的循環,直到加血線程回覆了血量
這是不好的解決方式,因爲會大量佔用CPU,拖慢性能

package charactor;
public class Hero{
    public String name;
    public float hp;
    public int damage;
    public synchronized void recover(){
        hp=hp+1;
    }  
    public synchronized void hurt(){
            hp=hp-1;   
    }
}
package multiplethread;
import java.awt.GradientPaint;
import charactor.Hero;
public class TestThread {
    public static void main(String[] args) {
        final Hero gareen = new Hero();
        gareen.name = "蓋倫";
        gareen.hp = 616;
        Thread t1 = new Thread(){
            public void run(){
                while(true){
                    //因爲減血更快,所以蓋倫的血量遲早會到達1
                    //使用while循環判斷是否是1,如果是1就不停的循環
                    //直到加血線程回覆了血量
                    while(gareen.hp==1){
                  	  	System.out.println("蓋倫的hp:" + gareen.hp);
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        continue;
                    }
                    gareen.hurt();
                    System.out.printf("t1 爲%s 減血1點,減少血後,%s的血量是%.0f%n",gareen.name,gareen.name,gareen.hp);
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        };
        t1.start();
 
        Thread t2 = new Thread(){
            public void run(){
                while(true){
                    gareen.recover();
                    System.out.printf("t2 爲%s 回血1點,增加血後,%s的血量是%.0f%n",gareen.name,gareen.name,gareen.hp);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
 
            }
        };
        t2.start();
    } 
}

在這裏插入圖片描述

使用wait和notify進行線程交互

在Hero類中:hurt()減血方法:當hp=1的時候,執行this.wait().
this.wait()表示 讓佔有this的線程等待,並臨時釋放佔有
進入hurt方法的線程必然是減血線程,this.wait()會讓減血線程臨時釋放對this的佔有。 這樣加血線程,就有機會進入recover()加血方法了。

recover() 加血方法:增加了血量,執行this.notify();
this.notify() 表示通知那些等待在this的線程,可以甦醒過來了。 等待在this的線程,恰恰就是減血線程。 一旦recover()結束, 加血線程釋放了this,減血線程,就可以重新佔有this,並執行後面的減血工作。
在這裏插入圖片描述

package charactor;
 
public class Hero {
    public String name;
    public float hp;
 
    public synchronized void recover() {
        hp = hp + 1;
        System.out.printf("%s 回血1點,增加血後,%s的血量是%.0f%n", name, name, hp);
        // 通知那些等待在this對象上的線程,可以醒過來了,如第20行,等待着的減血線程,甦醒過來
        this.notify();
    }
 
    public synchronized void hurt() {
        if (hp == 1) {
            try {
                // 讓佔有this的減血線程,暫時釋放對this的佔有,並等待
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        hp = hp - 1;
        System.out.printf("%s 減血1點,減少血後,%s的血量是%.0f%n", name, name, hp);
    }
}
package multiplethread;
import java.awt.GradientPaint;
import charactor.Hero;
public class TestThread7{
    public static void main(String[] args) {
        final Hero gareen = new Hero();
        gareen.name = "蓋倫";
        gareen.hp = 616;
             
        Thread t1 = new Thread(){
            public void run(){
                while(true){
                    gareen.hurt();
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        };
        t1.start();
        Thread t2 = new Thread(){
            public void run(){
                while(true){
                    gareen.recover();
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        };
        t2.start();
    }    
}

關於wait、notify和notifyAll

留意wait()和notify() 這兩個方法是什麼對象上的?

public synchronized void hurt() {
  this.wait();
}
public synchronized void recover() {
   this.notify();
}

這裏需要強調的是,wait方法和notify方法,並不是Thread線程上的方法,它們是Object上的方法。

因爲所有的Object都可以被用來作爲同步對象,所以準確的講,wait和notify是同步對象上的方法。

wait()的意思是: 讓佔用了這個同步對象的線程,臨時釋放當前的佔用,並且等待。 所以調用wait是有前提條件的,一定是在synchronized塊裏,否則就會出錯。

notify() 的意思是,通知一個等待在這個同步對象上的線程,你可以甦醒過來了,有機會重新佔用當前對象了。

notifyAll() 的意思是,通知所有的等待在這個同步對象上的線程,你們可以甦醒過來了,有機會重新佔用當前對象了。

練習-生產者消費者問題

生產者消費者問題是一個非常典型性的線程交互的問題。

  1. 使用棧來存放數據
    1. 把棧改造爲支持線程安全
    2. 把棧的邊界操作進行處理,當棧裏的數據是0的時候,訪問pull的線程就會等待。 當棧裏的數據是200的時候,訪問push的線程就會等待
  2. 提供一個生產者(Producer)線程類,生產隨機大寫字符壓入到堆棧
  3. 提供一個消費者(Consumer)線程類,從堆棧中彈出字符並打印到控制檯
  4. 提供一個測試類,使兩個生產者和三個消費者線程同時運行,結果類似如下 :
    在這裏插入圖片描述

線程安全的棧

package multiplethread;
import java.util.ArrayList;
import java.util.LinkedList;
public class MyStack<T> {
    LinkedList<T> values = new LinkedList<T>();
    public synchronized void push(T t) {
        while(values.size()>=200){
            try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        this.notifyAll();
        values.addLast(t);
    }
    public synchronized T pull() {
        while(values.isEmpty()){
            try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        this.notifyAll();
        return values.removeLast();
    }
    public T peek() {
        return values.getLast();
    }
}

生產者線程

package multiplethread;
public class ProducerThread extends Thread{
    private MyStack<Character> stack;
    public ProducerThread(MyStack<Character> stack,String name){
        super(name);
        this.stack =stack;
    }
     
    public void run(){
        while(true){
            char c = randomChar();
            System.out.println(this.getName()+" 壓入: " + c);
            stack.push(c);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        } 
    }
    public char randomChar(){
        return (char) (Math.random()*('Z'+1-'A') + 'A');
    }
}

消費者線程

package multiplethread;
public class ConsumerThread extends Thread{
    private MyStack<Character> stack;
    public ConsumerThread(MyStack<Character> stack,String name){
        super(name);
        this.stack =stack;
    }
     
    public void run(){
        while(true){
            char c = stack.pull();
            System.out.println(this.getName()+" 彈出: " + c);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
         
    }
}

測試

package multiplethread;
public class TestThread {
    public static void main(String[] args) {
        MyStack<Character> stack = new MyStack<>();
        new ProducerThread(stack, "Producer1").start();
        new ProducerThread(stack, "Producer2").start();
        new ConsumerThread(stack, "Consumer1").start();
        new ConsumerThread(stack, "Consumer2").start();
        new ConsumerThread(stack, "Consumer3").start();                      
    }      
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章