JAVA---线程间通信

线程间的通信:

其实就是多个线程在操作同一个资源,但是操作的功能不同

实例详解:

我们要用多线程实现两个对象,一个叫张三的男性和李四的女性一次交替打印出来,如下

张三---男
李四---女
张三---男
李四---女
张三---男
李四---女

如何实现:

利用多线程,第一个线程写入(input)用户,另一个线程(out)用户

解决的问题:

  1. 进出资源搭配不正确的问题,线程抢夺资源的问题---加锁
  2. 防止一个线程抢占的次数多的问题,出现输出和输入次数不匹配---wait(),notify(),notifyAll()
  3. 防止死锁,只唤醒对方,不唤醒本方---Lock

 

代码实现:

  • 共享资源源实体类
class Res {
  public String Sex;
  public String Name;
}
  •  输入线程资源
class IntThrad extends Thread {
  private Res res;    //不直接赋值,说明操作的同一对象

  public IntThrad(Res res) {
    this.res = res;
  }

  @Override
  public void run() {
    int count = 0;
    while (true) {
            if (count == 0) {
                res.Name = "张三";
                res.Sex = "男";
            } else {
                res.Name = "李四";
                res.Sex = "女";
            }
            count = (count + 1) % 2;
        }
  }
}
  •  输出线程
class OutThread extends Thread {
  private Res res;

  public OutThread(Res res) {
    this.res = res;
  }

  @Override
  public void run() {
    while (true) {
            System.out.println(res.Name + "---" + res.Sex);
    }
  }
}
  •  运行代码
public class ThreadDemo01 {
  public static void main(String[] args) {
    Res res = new Res();
    //进出线程传入的是同一对象
    InputThread inputThread = new InputThread(res);
    OutThrad outThrad = new OutThrad(res);
    inputThread.start();
    outThrad.start();
  }
}
  • 运行结果:
张三---男
张三---女
李四---女
张三---女
  • 出现的问题:

 人名和性别不匹配

  • 为什么会出现这样的情况

数据发生错乱,造成线程安全问题(消费者可能还没读,生产者就已经修改了),两个run方法存放着代码,但是都在操作同一资源,在操作同一资源的时候,就有可能刚把张三输入,输出就获得了执行权,就输出了,还没有等男这个属性赋值就输出了,输出的是上一个性别属性,可能是男,可能是女

  • 如何解决这样的情况

加锁(synchronized),锁的对象是谁(找唯一对象,intThread.class,outThread.class,Res也可以)

 

  • 输入线程加上synchronized
class IntThrad extends Thread {
  private Res res;

  public IntThrad(Res res) {
    this.res = res;
  }

  @Override
  public void run() {
    int count = 0;
    while (true) {
        synchronized (res) {
            if (count == 0) {
                res.Name = "张三";
                res.Sex = "男";
            } else {
                res.Name = "李四";
                res.Sex = "女";
            }
            count = (count + 1) % 2;
        }

    }
   }
}
  •  输出线程加上synchronized
class Res {
  public String userName;
  public String sex;
}

class InputThread extends Thread {
  private Res res;

  public InputThread(Res res) {
    this.res = res;
  }

  @Override
  public void run() {
    int count = 0;
    while (true) {
        synchronized (res) {
          if (count == 0) {
            res.Name = "张三";
            res.sex = "男";
          } else {
            res.Name = "李四";
            res.sex = "女";
          }
            count = (count + 1) % 2;
        }
    }
  }
}

class OutThrad extends Thread {
  private Res res;

  public OutThrad(Res res) {
    this.res = res;
  }

  @Override
  public void run() {
    while (true) {
        synchronized (res) {
            System.out.println(res.Name + "---" + res.sex);
        }
    }
  }
}

  •  运行代码
public class ThreadDemo01 {
  public static void main(String[] args) {
    Res res = new Res();
    InputThread inputThread = new InputThread(res);
    OutThrad outThrad = new OutThrad(res);
    inputThread.start();
    outThrad.start();
  }
}
  •  输出结果:
张三---男
张三---男
李四---女
张三---男
  •  出现的问题:

张三多次出现,李四出现的次数少,不是交替出现,,我们要实现一进一出

  • 为什么会出现这样的问题:

因为input和output都有可能多次抢到执行权

  • 解决方案:

添加标记,输入进去就唤醒输出的线程,wait()和notify(),notifyAll方法(一等一醒)---等待唤醒机制

wait()、notify、notifyAll()方法

  1. wait()、notify()、notifyAll()是三个定义在Object类里的方法,可以用来控制线程的状态。
  2. 这三个方法最终调用的都是jvm级的native方法,随着jvm运行平台的不同可能有些许差异。
  3. 如果对象调用了wait方法就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。
  4. 如果对象调用了notify方法就会通知某个正在等待这个对象的控制权的线程可以继续运行。
  5. 如果对象调用了notifyAll方法就会通知所有等待这个对象控制权的线程继续运行。
  6. 注意:一定要在线程同步中使用,并且是同一个锁的资源。 因为wait(),notify(),notifyAll()都使用在同步中,因为要对持有监控器(锁)的线程操作,所以要使用在同步中,因为只有同步才具有锁
class Res {
  public String Sex;
  public String Name;
  //线程通讯标识
  public boolean flag = false;
}

class IntThrad extends Thread {
  private Res res;

  public IntThrad(Res res) {
    this.res = res;
  }

  @Override
  public void run() {
    int count = 0;
    while (true) {
        synchronized (res) {
            if (res.flag) {
                try {
                   // 当前线程变为等待,但是可以释放锁
                    res.wait();
                } catch (Exception e) {

                }
            }
            if (count == 0) {
                res.Name = "余胜军";
                res.Sex = "男";
            } else {
                res.Name = "小紅";
                res.Sex = "女";
            }
            count = (count + 1) % 2;
            res.flag = true;
            // 唤醒res线程池中的线程,默认是线程池中的第一个
            res.notify();
        }
      }
    }
}

class OutThread extends Thread {
  private Res res;

  public OutThread(Res res) {
    this.res = res;
  }

  @Override
  public void run() {
    while (true) {
        synchronized (res) {
            if (!res.flag) {
                try {
                    res.wait();
                } catch (Exception e) {
                    // TODO: handle exception
                }
            }
            System.out.println(res.Name + "--" + res.Sex);
            res.flag = false;
            res.notify();
        }
    }
  }
}

public class ThreaCommun {
  public static void main(String[] args) {
    Res res = new Res();
    IntThrad intThrad = new IntThrad(res);
    OutThread outThread = new OutThread(res);
    intThrad.start();
    outThread.start();
  }
}
  • wait()方法会抛异常 

 

 

 

  • 为什么这些操作线程的方法要定义Object类中呢?

因为这些方法在操作同步中线程时,都必须要标识他们所操作线程持有的锁,只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒,不可以对不同锁中的线程进行唤醒,也就是说,等待和唤醒必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中

  • if和while的问题

生产者消费者问题

有多个线程生产和有多个线程消费的问题

出现的问题:两个生产,一个消费,或者一个生产,消费了两个的问题

错误原因:(用if判断标记)生产者把生产者唤醒了,而且不需要判断标记,不是把消费者唤醒,就会出现不对称的情况

解决办法:用while判断,但是又会发生死锁,在while判断的基础上,唤醒全部(notifyAll),既唤醒本方,又唤醒对方

对于多个生产者和消费者,为什么要定义while判断标记?

因为想让被唤醒的线程再一次判断标记,以防出现条件不满足就直接执行后面代码的情况(因为是多线程)

  • 为什么定义notifyAll?

因为需要唤醒对方线程,因为只用notify,容易出现只唤醒本方线程的情况,导致程序中的所有线程都等待。发生死锁的问题

  • 死锁怎么产生的?

由于资源有限,加入有两个资源只有一份,每一个线程都需要两个资源才能继续执行,但是在执行一个线程的时候,获取了第一个资源,还未获取第二个资源的时候,sleep了一下,就让第二个进行进来了,第二个进程获取了第二个资源(程序这个设置的,有意为之),但是获取不了第一个资源,一直在等着被释放,而这时第一个线程sleep时间到了之后,因为缺少第二个资源而无法继续进行

  • 解决办法:

我们要只唤醒对方,不唤醒本方怎么办?

解决方案:Lock,JDk1.5中提供了多线程升级解决方案,将同步synchronize替换成现实Lock操作,将Object中的wait,notify,notifyAll,替换成了Condition对象,该对象可以Lock锁,进行获取

class Res {
public String Name;
public String sex;
public boolean flag = false;
Lock lock = new ReentrantLock();
}

class InputThread extends Thread {
  private Res res;
  Condition newCondition;
  public InputThread(Res res,   Condition newCondition) {
    this.res = res;
    this.newCondition=newCondition;
  }

  @Override
  public void run() {
    int count = 0;
    while (true) {
        // synchronized (res) {

        try {
            res.lock.lock();
            if (res.flag) {
                try {
//                      res.wait();
                    newCondition.await();
                } catch (Exception e) {
                    // TODO: handle exception
                }
            }
            if (count == 0) {
                res.Name = "张三";
                res.sex = "男";
            } else {
                res.Name = "李四";
                res.sex = "女";
            }
            count = (count + 1) % 2;
            res.flag = true;
//              res.notify();
            newCondition.signal();
         } catch (Exception e) {
            // TODO: handle exception
          }finally {
            res.lock.unlock();
       }
   }

    // }
    }
}

class OutThrad extends Thread {
private Res res;
private Condition newCondition;
public OutThrad(Res res,Condition newCondition) {
    this.res = res;
    this.newCondition=newCondition;
}

@Override
public void run() {
    while (true) {
//          synchronized (res) {
        try {
            res.lock.lock();
            if (!res.flag) {
                try {
//                      res.wait();
                    newCondition.await();
                } catch (Exception e) {
                    // TODO: handle exception
                }
            }
            System.out.println(res.userName + "," + res.sex);
            res.flag = false;
  //                res.notify();
            newCondition.signal();
        } catch (Exception e) {
            // TODO: handle exception
        }finally {
            res.lock.unlock();
        }
//          }
    }

  }
}

public class ThreadDemo01 {

   public static void main(String[] args) {
    Res res = new Res();
    Condition newCondition = res.lock.newCondition();
    InputThread inputThread = new InputThread(res,newCondition);
    OutThrad outThrad = new OutThrad(res,newCondition);
    inputThread.start();
    outThrad.start();
  }
}
  • 停止线程

如何停止线程?

只要一种,run方法结束,开始多线程运行,运行代码通常是循环结构。

只要控制住循环,就可以让run方法结束,也就是线程结束(标记一下,符合标记,break)

特殊情况:

当线程处于冻结状态,就不会读取到标记,那么线程就不会结束(父线程结束了,但是子线程还处于冻结状态,就停止不掉)

解决方案:

当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除,强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束

Thread类提供该方法:interrupt()

 

  • 守护线程

setDaemon(boolean on):将该线程标记为守护线程或用户线程,当被守护的线程消失,那么守护线程也消失(后台线程--当前台线程结束,后台线程也自动结束)

 

  • join()

当A线程执行到B线程的.join()方法时,A就会等B线程都执行完,A才会继续执行

可以用来临时加入线程执行

 

  • yield()

暂停当前正在执行的线程对象,并执行其他线程

可以稍微减缓线程执行的频率

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值