zoukankan      html  css  js  c++  java
  • Java自学-多线程 交互

    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();
                               
        }
               
    }
    
  • 相关阅读:
    vue使用elementui合并table
    使用layui框架导出table表为excel
    vue使用elementui框架,导出table表格为excel格式
    前台传数据给后台的几种方式
    uni.app图片同比例缩放
    我的博客
    【C语言】取16进制的每一位
    SharePoint Solution 是如何部署的呢 ???
    无效的数据被用来用作更新列表项 Invalid data has been used to update the list item. The field you are trying to update may be read only.
    SharePoint 判断用户在文件夹上是否有权限的方法
  • 原文地址:https://www.cnblogs.com/jeddzd/p/12388362.html
Copyright © 2011-2022 走看看