多线程 使用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();                      
    }      
}
全部评论

相关推荐

我见java多妩媚:大外包
点赞 评论 收藏
分享
双非坐过牢:非佬,可以啊10.28笔试,11.06评估11.11,11.12两面,11.19oc➕offer
点赞 评论 收藏
分享
评论
点赞
收藏
分享
正在热议
# 25届秋招总结 #
443459次浏览 4523人参与
# 春招别灰心,我们一人来一句鼓励 #
42266次浏览 539人参与
# 阿里云管培生offer #
120459次浏览 2220人参与
# 地方国企笔面经互助 #
7973次浏览 18人参与
# 同bg的你秋招战况如何? #
77249次浏览 569人参与
# 实习必须要去大厂吗? #
55816次浏览 961人参与
# 北方华创开奖 #
107475次浏览 600人参与
# 虾皮求职进展汇总 #
116395次浏览 887人参与
# 如果你有一天可以担任公司的CEO,你会做哪三件事? #
11702次浏览 289人参与
# 实习,投递多份简历没人回复怎么办 #
2455021次浏览 34861人参与
# 提前批简历挂麻了怎么办 #
149962次浏览 1979人参与
# 在找工作求抱抱 #
906124次浏览 9423人参与
# 如果公司给你放一天假,你会怎么度过? #
4764次浏览 55人参与
# 你投递的公司有几家约面了? #
33209次浏览 188人参与
# 投递实习岗位前的准备 #
1196058次浏览 18550人参与
# 机械人春招想让哪家公司来捞你? #
157650次浏览 2267人参与
# 双非本科求职如何逆袭 #
662406次浏览 7397人参与
# 发工资后,你做的第一件事是什么 #
12808次浏览 62人参与
# 工作中,努力重要还是选择重要? #
35929次浏览 384人参与
# 简历中的项目经历要怎么写? #
86943次浏览 1516人参与
# 参加完秋招的机械人,还参加春招吗? #
20154次浏览 240人参与
# 我的上岸简历长这样 #
452080次浏览 8089人参与
牛客网
牛客企业服务