• 线程通信---等待与唤醒机制


      之前有5篇文章,对于线程知识做了一些简单的梳理,这篇文章还是准备以案例实现的形式来记录一下线程之间的通信—等待与唤醒机制。
      在了解等待与唤醒机制之前,首先思考一下线程之间为什么需要进行通信?
      我们都知道,要想能够去执行一个线程,首先这个线程需要获取CPU的执行权,当这个线程执行完毕之后,就会释放CPU资源,并发执行的时候,剩下的处于就绪状态的线程就会一起去争夺CPU的执行权,谁抢到谁就执行。但是在开发过程中,我们可能需要多个线程进行协调配合来完成一件事。也就是说,我们需要线程之间有规律的去执行任务。这就需要线程之间相互通信了。

    等待与唤醒机制

      通过上面的描述,可以知道,当多个线程共同去争夺同一个cpu资源的时候,只能有一个线程能够抢到,其他没抢到的就会进入阻塞状态,等待抢到cpu资源的线程执行结束再去抢夺。通过一个简单的例子说明一下。烤鸭店老板A将烤鸭生产好之后,就叫顾客B过来吃。这个小案例中,就提现了类似线程中等待与唤醒机制。

    • A先生产,此时B在等待A生产好。
    • A生产好之后,通知B来吃,相当于B被A唤醒。
      线程之间的通信依靠的是wait()notify()notifyAll()方法进行协调。这三个方法都是定义在了Object类中,

    一、为什么要定义在Object类中?
      一提到定义在Object中就会想到同步锁,之前的一篇文章:线程安全—实现安全卖票中,就介绍了Java中每个对象有且仅有一个对象锁。也就是说所有的java对象都可以是同步对象,既然所有对象都能是同步对象,而且有wait()notify()notifyAll()方法,当然应该将这三个方法定义到Object超类中。

    二、等待与唤醒方法基本概念:

    • wait():
        当线程A调用wait()方法后,释放同步锁,进入阻塞状态,然后加入到等待锁对象的队列中。
    • notify():
        线程B获取到同步锁之后,调用notify()方法,从等待锁的队列中唤醒一个线程(被唤醒的线程状态由等待转变成就绪,等线程B执行完毕释放了锁资源之后,被唤醒的线程获取到锁之后就会去执行该线程)
    • notifyAll():
        线程B获取到同步锁之后,调用notifyAll()方法,会唤醒等待锁队列中所有的线程,等待线程B执行完之后释放锁资源,被唤醒的线程去争夺锁资源,获取到锁对象的线程会去执行相应的逻辑。

    三、notify()notifyAll()的区别总结:

       notify()方法 仅仅会去通知等待队列中的其中一个线程,并且我们并不知道哪个线程会被唤醒,但是notifyAll()方法会唤醒等待队列中的所有处于等待状态的线程(如果此时我们的等待队列中只有一个处于等待状态的线程,那么两种唤醒方法的效果一样,但是如果等待队列中有两个或两个以上的等待状态线程,那么就需要主要两种唤醒方法的区别了)

    四、sleep()方法和wait()方法有何区别?

      乍一看!线程中的sleep()方法和wait()方法可能是比较容易搞混的,那么它俩究竟有什么区别呢?

    可以结合:线程的生命周期来看。

    1. sleep()
        他是定义在Thread类中的一个方法,当调用此方法的时候,线程可以由RUNNING状态转换为TIMED_WAITING状态,
      线程执行此方法的时候,将会释放CPU执行权,但是该线程依然会持有当前拥有的锁对象,(它释放了CPU的执行权之后,其他线程可以使用此CPU执行权去执行不依赖此对象锁的任务)。此方法用在什么位置没有特殊要求。

    2. wait()
        他是定义在Object类中的一个方法,当调用此方法的时候,线程可以由RUNNING状态转换为WAITING状态,此状态也可以理解为“无线等待”状态,他需要配合notify()notifyAll()方法来唤醒线程。
      另外,线程执行此方法的时候,将会释放CPU的执行权和持有的锁。此方法必须要用在synchronized同步代码块中。

      以上方法需要用在同步代码块/方法中。调用wait()方法和notify()/notifyAll()方法的锁对象必须是同一个。

    情景:就以上文中的货物打包为例;

    案例
      按照惯例,我们还是以一个简单的例子对线程之间的通信机制进行代码实现。

    情景:就以上文中的货物打包为例;烤鸭店老板A生产好食物之后,通知B过来吃。B吃完之后,通知A继续生产制作

    • 实体类
    public class Food {
    
    	/**
    	 * 标记 true:有食物   false:没有食物
    	 */
    	private boolean flag=false;
    
    	public Food() {
    	}
    
    	public boolean isFlag() {
    		return flag;
    	}
    
    	public void setFlag(boolean flag) {
    		this.flag = flag;
    	}
    
    }
    
    • 消费者类(顾客B)
    public class Consumer implements Runnable {
    
    	private Food food;
    
    	public Consumer(Food food) {
    		this.food = food;
    	}
    
    	//设置线程任务----消耗食物
    	@Override
    	public void run() {
    
    		while (true) {
    			//同步代码块
    			synchronized (food) {
    				while (!food.isFlag()) {
    					//没有食物,消费线程进入阻塞状态
    					try {
    						food.wait();
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    				}
    				for (int i = 1; i < 11; i++) {
    					System.out.println("正在消耗===>" + i + "号食物");
    					try {
    					//让线程等待500ms,不然吃太快容易噎着
    						Thread.sleep(500);
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					if (i == 10) {
    						food.setFlag(false);
    					}
    				}
    				food.notify();
    			}
    		}
    	}
    }
    
    • 生产者类(烤鸭店老板A)
    public class Product implements Runnable {
    
    	//设置线程任务---生产食物
    
    	private Food food;
    
    	public Product(Food food) {
    		this.food = food;
    	}
    
    	@Override
    	public void run() {
    
    		while (true) {
    			synchronized (food) {
    				while (food.isFlag()) {
    					//有食物,生产线程进入阻塞状态
    					try {
    						food.wait();
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    				}
    				for (int i = 1; i < 11; i++) {
    					System.out.println("===正在生产===>" + i + "号食物");
    					try {
    						Thread.sleep(500);
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					food.setFlag(true);
    				}
    				food.notify();
    			}
    		}
    	}
    }
    
    • 测试类
    public class test01 {
    
    	public static void main(String[] args) {
    		//创建一个食物对象
    		Food food = new Food();
    		//创建一个生产者对象
    		new Thread(new Product(food)).start();
    		//创建一个消费者对象
    		new Thread(new Consumer(food)).start();
    	}
    }
    
    • 启动测试类
    ===正在生产===>1号食物
    ===正在生产===>2号食物
    ===正在生产===>3号食物
    ===正在生产===>4号食物
    ===正在生产===>5号食物
    ===正在生产===>6号食物
    ===正在生产===>7号食物
    ===正在生产===>8号食物
    ===正在生产===>9号食物
    ===正在生产===>10号食物
    正在消耗===>1号食物
    正在消耗===>2号食物
    正在消耗===>3号食物
    正在消耗===>4号食物
    正在消耗===>5号食物
    正在消耗===>6号食物
    正在消耗===>7号食物
    正在消耗===>8号食物
    正在消耗===>9号食物
    正在消耗===>10号食物
    ===正在生产===>1号食物
    ===正在生产===>2号食物
    ===正在生产===>3号食物
    ===正在生产===>4号食物
    ===正在生产===>5号食物
    ===正在生产===>6号食物
    ……后面省略
    

      上面就是线程通信中的一个简单的生产者/消费者案例,通过使用notify()wait()方法来控制线程的唤醒和等待。让线程按照我们既定的规律来执行,这也就是线程之间进行通信协调工作的原理。

  • 相关阅读:
    HTML CSS整理笔记
    2020软件工程最后一次作业
    form表单的基本用法
    图片预加载和懒加载(2)——懒加载
    ES6——promise基础
    图片预加载和懒加载(2)——预加载
    图片预加载和懒加载(1)
    js时间——转换为我们需要的格式
    原生js瀑布流
    富文本——小程序中使用特殊符号及标签
  • 原文地址:https://www.cnblogs.com/wgty/p/12810475.html
Copyright © 2020-2023  润新知