线程之间的相互通信
在介绍线程相互通信前为大家介绍一下有关线程通信的一些知识
线程的状态(线程的生命周期)
线程的状态 NEW 新建状态,刚刚创建完成还没开启的状态 RUNNABLE 可运行状态,有资格执行,可能在执行中,有可能不是在执行中 BLOCKED 锁阻塞状态,要等待其他线程释放锁对象 WAITING 无限等待,一个线程等待另一个线程执行一个(唤醒)动作 TIMED_WAITING 计时等待,这一状态一直保持到超过规定的时间,或者收到唤醒动作 TERMINATED 死亡状态,任务执行完毕的状态
有关线程通信的方法介绍
以下方法都来自与object类,因为object是所有类的父类所以每一个类都可以使用以下方法
void wait() 等待,让出cpu进入等待状态(如果一个线程内调用了该方法,那么该线程就停止运行,等待其他线程唤醒,或者其他线程调用notifAll方法) void notify() 唤醒,随机唤醒一个正在等待的线程,让其进入可运行状态(解除了调用wait方法线程的等待状态,让其变成可运行状态) void notifyAll() 唤醒所以进入等待状态的线程,让其都进入可运行状态
线程等待与唤醒的概述
线程的等待与唤醒又称为线程之间的通信,等待与唤醒机制是实现两个或多个线程在执行任务过程相互配合相互协作的一种技术。
线程之间相互通信带来的好处
先看看下面的代码运行结果(下面代码没有使用到线程通信的技术)
public class Dome01 { public static void main(String[] args) { //创建资源对象 Person person = new Person(); //创建两哥线程 传入Runnable的实现类 并且把资源对象传入构造方法 Thread thread1 = new Thread(new ConsumerThread(person)); Thread thread2 = new Thread(new ProductThread(person)); //开启线程 thread1.start(); thread2.start(); } } //创建消费对象Runnable的实现类 作用用来消费person里面的数据 就是把person里面的name和打印到控制台 class ConsumerThread implements Runnable { //创建资源对象 Person person; //有参构造 因为消费者和生产者要使用一个资源对象,所以创建构造方法传入资源对象 public ConsumerThread(Person p) { person = p; } @Override public void run() { //创建一个变量,使每一次生产的数据都不一样 boolean b = true; while (true) { //因为两个线程使用的是同一个资源对象 所以可以使用这个资源对象作为锁对象 synchronized (person) { //使用判断生产不同数据 if (b){ person.name = "如花"; person.age = 18; //修改变量值使他下一次循环进入else b = false; } else { person.name = "凤姐"; person.age = 100; //修改变量值使他下一次循环进入else b = true; } } } } } //创建生产者对象Runnable的实现类 生产数据就是给person的name和age赋值 class ProductThread implements Runnable { //创建资源对象 Person person; //有参构造 因为消费者和生产者要使用一个资源对象,所以创建构造方法传入资源对象 public ProductThread(Person p) { person = p; } @Override public void run() { //死循环消费数据 while (true) { //因为两个线程使用的是同一个资源对象 所以可以使用这个资源对象作为锁对象 synchronized (person) { //判断生产者是否已经生产了数据,如果生产了数据就打印 if (person.name != null && person.age != 0) { System.out.println(person.name + " " + person.age); } } } } } //资源对象 让一个线程去给他赋值一个线程去消费数据,就是使用另一个线程给他赋的值 class Person { String name; int age; }
程序结果运行如下
程序输出的要么全部是 ” 如花 18 “ 要么全部是 “凤姐 100” ,如果我们现在的条件是要 ” 如花 18 “ , “凤姐 100”
要交替输出,我们如果不使用线程之间的相互通信是没办法做到的,因为不利用线程的相互通信技术我们没办法让线程和线程之间产生关系,也就是我们没办法通知另一条线程,说我输出完了让你赋值。
下面是使用线程通信技术完成两行交替输出
public class Dome01 { public static void main(String[] args) { //创建资源对象 Person person = new Person(); //创建两哥线程 传入Runnable的实现类 并且把资源对象传入构造方法 Thread thread1 = new Thread(new ConsumerThread(person)); Thread thread2 = new Thread(new ProductThread(person)); //开启线程 thread1.start(); thread2.start(); } } //创建消费对象Runnable的实现类 作用用来消费person里面的数据 就是把person里面的name和打印到控制台 class ConsumerThread implements Runnable { //创建资源对象 Person person; //有参构造 因为消费者和生产者要使用一个资源对象,所以创建构造方法传入资源对象 public ConsumerThread(Person p) { person = p; } @Override public void run() { //创建一个变量,使每一次生产的数据都不一样 boolean b = true; while (true) { //因为两个线程使用的是同一个资源对象 所以可以使用这个资源对象作为锁对象 synchronized (person) { //使用判断生产不同数据 if (b){ person.name = "如花"; person.age = 18; //修改变量值使他下一次循环进入else b = false; } else { person.name = "凤姐"; person.age = 100; //修改变量值使他下一次循环进入else b = true; } try { //唤醒全部线程 person.notifyAll(); //调用该方法线程进入无限等待状态 ,并且释放锁对象 等待其他线程唤醒 person.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } //创建生产者对象Runnable的实现类 生产数据就是给person的name和age赋值 class ProductThread implements Runnable { //创建资源对象 Person person; //有参构造 因为消费者和生产者要使用一个资源对象,所以创建构造方法传入资源对象 public ProductThread(Person p) { person = p; } @Override public void run() { //死循环消费数据 while (true) { //因为两个线程使用的是同一个资源对象 所以可以使用这个资源对象作为锁对象 synchronized (person) { //判断生产者是否已经生产了数据,如果生产了数据就打印 if (person.name != null && person.age != 0) { System.out.println(person.name + " " + person.age); try { //等该方法打印完另一个线程所赋的值, 就唤醒全部线程 person.notifyAll(); //然后调用wait方法进入无限等待状态,并且释放锁 person.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else{ try { //如果进入了else里面说明 person对象还没有赋值,所以就调用wait方法让线程进入无限等待状态,把zpu资源让给其他线程 person.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } //资源对象 让一个线程去给他赋值一个线程去消费数据,就是使用另一个线程给他赋的值 class Person { String name; int age; }
程序运行结果
使用线程通信技术我们就可以让线程之间存在一定的关系,而不是每一个线程读是独立运行的,与其他线程无关。
使用wait,notify,notifyAll方法注意事项
这些方法必须是锁对象调用
必须在同步代码块,或者是同步方法和cock和unlcok方法中间调用。