358、Java中级13 -【多线程 - 死锁、交互】 2020.06.26

1、死锁

当业务比较复杂,多线程应用里有可能会发生死锁

在这里插入图片描述

  1. 线程1 首先占有对象1,接着试图占有对象2
  2. 线程2 首先占有对象2,接着试图占有对象1
  3. 线程1 等待线程2释放对象2
  4. 与此同时,线程2等待线程1释放对象1
    就会。。。一直等待下去,直到天荒地老,海枯石烂,山无棱 ,天地合。。。
package multiplethread;
   
import charactor.Hero;
    
public class TestThread {
      
    public static void main(String[] args) {
        final Hero ahri = new Hero();
        ahri.name = "九尾妖狐";
        final Hero annie = new Hero();
        annie.name = "安妮";
         
        Thread t1 = new Thread(){
            public void run(){
                //占有九尾妖狐
                synchronized (ahri) {
                    System.out.println("t1 已占有九尾妖狐");
                    try {
                        //停顿1000毫秒,另一个线程有足够的时间占有安妮
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                     
                    System.out.println("t1 试图占有安妮");
                    System.out.println("t1 等待中 。。。。");
                    synchronized (annie) {
                        System.out.println("do something");
                    }
                }  
                 
            }
        };
        t1.start();
        Thread t2 = new Thread(){
            public void run(){
                //占有安妮
                synchronized (annie) {
                    System.out.println("t2 已占有安妮");
                    try {
                         
                        //停顿1000毫秒,另一个线程有足够的时间占有暂用九尾妖狐
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println("t2 试图占有九尾妖狐");
                    System.out.println("t2 等待中 。。。。");
                    synchronized (ahri) {
                        System.out.println("do something");
                    }
                }  
                 
            }
        };
        t2.start();
   }
        
}

2、JAVA 线程之间的交互 WAIT和NOTIFY

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

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

3、不好的解决方式

故意设计减血线程频率更高,盖伦的血量迟早会到达1
减血线程中使用while循环判断是否是1,如果是1就不停的循环,直到加血线程回复了血量
这是不好的解决方式,因为会大量占用CPU,拖慢性能

  • Hero.java
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;
    }
   
}
  • TestThread.java
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();
           
    }
        
}

4、使用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,并执行后面的减血工作。

在这里插入图片描述

  • Hero.java
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;
    }
 
}
  • TestThread.java
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;
    }
 
}

5、关于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() 的意思是,通知所有的等待在这个同步对象上的线程,你们可以苏醒过来了,有机会重新占用当前对象了。

6、参考链接

[01] How2j - 多线程 - 死锁
[02] How2j - 多线程 - 交互

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值