美文网首页Java自学
Java自学-多线程 交互

Java自学-多线程 交互

作者: Wester西人 | 来源:发表于2020-03-01 09:17 被阅读0次

    Java 线程之间的交互 wait和notify

    线程之间有交互通知的需求,考虑如下情况:
    有两个线程,处理同一个英雄。
    一个加血,一个减血。

    减血的线程,发现血量=1,就停止减血,直到加血的线程为英雄加了血,才可以继续减血

    步骤 1 : 不好的解决方式

    故意设计减血线程频率更高,盖伦的血量迟早会到达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;   
        }
          
        public void attackHero(Hero h) {
            h.hp-=damage;
            System.out.format("%s 正在攻击 %s, %s的血变成了 %.0f%n",name,h.name,h.name,h.hp);
            if(h.isDead())
                System.out.println(h.name +"死了!");
        }
       
        public boolean isDead() {
            return 0>=hp?true:false;
        }
       
    }
    

    .

    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){
                            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();
               
        }
            
    }
    

    步骤 2 : 使用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,并执行后面的减血工作。

    使用wait和notify进行线程交互
    package charactor;
     
    public class Hero {
        public String name;
        public float hp;
     
        public int damage;
     
        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);
        }
     
        public void attackHero(Hero h) {
            h.hp -= damage;
            System.out.format("%s 正在攻击 %s, %s的血变成了 %.0f%n", name, h.name, h.name, h.hp);
            if (h.isDead())
                System.out.println(h.name + "死了!");
        }
     
        public boolean isDead() {
            return 0 >= hp ? true : false;
        }
     
    }
    

    .

    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){
                           
                        //无需循环判断
    //                    while(gareen.hp==1){
    //                        continue;
    //                    }
                           
                        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();
                 
        }
              
    }
    

    步骤 3 : 关于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.1 把栈改造为支持线程安全
      1.2 把栈的边界操作进行处理,当栈里的数据是0的时候,访问pull的线程就会等待。 当栈里的数据是200的时候,访问push的线程就会等待
    2. 提供一个生产者(Producer)线程类,生产随机大写字符压入到堆栈
    3. 提供一个消费者(Consumer)线程类,从堆栈中弹出字符并打印到控制台
    4. 提供一个测试类,使两个生产者和三个消费者线程同时运行,结果类似如下 :
      在这里插入图片描述
      答案 :

    MyStack.java

    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();
        }
    }
    

    ProducerThread.java

    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');
        }
         
    }
    

    ConsumerThread.java

    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();
                }
            }
             
        }
         
        public char randomChar(){
            return (char) (Math.random()*('Z'+1-'A') + 'A');
        }
         
    }
    

    TestThread.java

    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自学-多线程 交互

        本文链接:https://www.haomeiwen.com/subject/okntkhtx.html