JavaSE 多线程——wait、notify与生产者与消费者模式

Java自我学习路线

  • 一、简单概述
  • 二、wait()
    • wait()和Thread.sleep()的比较
  • 三、notify()
  • 四、生产者和消费者模式

一、简单概述

  • 在Java中,任何一个Java对象都有wait和notify方法,wait和notify方法不是线程对象的方法,不通过线程对象调用
  • wait与notify是本地方法,并且被 final 修饰,无法被重写,建立在synchronized线程同步的基础上

二、wait()

  • 调用某个对象的wait() 方法能让当前线程处于等待状态,并将当前线程置入锁对象的等待队列中,直到notify或被中断为止,另外当前线程必须拥有此对象的Monitor(即锁,或者叫管程)
  • wait() 方法只能在同步方法或同步代码块中使用,并且synchronized后锁的对象应该与调用wait() 方法的对象一样,否则抛出IllegalMonitorStateException异常,wait() 方法调用后立刻释放对象锁,当然wait() 方法需要释放锁,前提条件是它已经持有锁(在某一时刻,只有一个线程可以持有一个对象的锁)
  • 调用wait() 方法会暂时放弃已获得的锁,并将自身放入到该对象对应的Wait Set(Wait Set 数据结构没有强制规定,一般认为是一个循环双向链表结构)中,同时让出CPU资源(CPU的相关寄存器会记住当前位置的堆栈信息),然后进入等待状态,唤醒时,并且再次获得锁后(即使调用notify() 方法唤醒,但只要锁没有被释放,原等待线程因为未获得锁仍然无法继续执行),可以沿着之前执行点的状态继续向后执行(CPU记住的堆栈信息),另外值得注意的是,对线程等待的条件的判断要使用while而不是if来进行判断,这样在线程被唤醒后,会再次判断条件是否正真满足

wait()和Thread.sleep()的比较

  • 相同点:都会阻断线程的执行,使得线程进入阻塞状态
  • 不同点
    wait()是Object类的实例方法,sleep()是Thread类的静态方法
    调用wait() 之前,当前线程必须持有该对象的锁,在调用wait()之后,该对象锁会被释放;而sleep方法没有这一限制,并且也不会释放对象的锁
    调用wait() 之后线程会一直等待,除非其他线程调用notify() 或notifyAll() 方法,当当前线程再次获得对象锁时才恢复执行;而sleep方法在等待指定时间后被唤醒

三、notify()

  • 调用某个对象的notify()方法能够唤醒一个正在等待这个对象的Monitor的线程,如果有多个线程都在等待这个对象的Monitor,则随机唤醒其中一个线程
  • notify() 也必须在同步方法或同步代码块中调用,用来唤醒等待该对象的其他线程,notify() 方法调用后,当前线程不会立刻释放对象锁,要等到当前线程执行完毕后再释放锁
  • 每个锁对象都有2个队列,一个称为同步队列,存储获取锁失败的线程,另一个称为等待队列,存储调用 wait() 等待的线程,将线程唤醒实际上是将处于等待队列的线程移到同步队列中竞争锁
  • 调用notify() 方法的线程必须是该对象锁的拥有者(在某一时刻,只有一个线程可以持有一个对象的锁),线程成为对象锁的拥有者的三种方式:
    执行了该对象的实例同步方法
    执行了一段同步代码块,并且synchronized关键字后跟的就是该对象
    对于Class对象而言,执行了该类的静态同步方法

四、生产者和消费者模式

  • 使用wait() 方法和notify() 方法实现生产者和消费者模式
  • 生产线程负责生产,消费线程负责消费,生产线程和消费线程要达到均衡
  • wait() 方法和notify() 方法建立在线程同步的基础之上,因为多线程要同时操作一个仓库,有线程安全问题
import java.util.ArrayList;
import java.util.List;

public class ThreadTest15 {
     
	public static void main(String[] args) {
     
		
		// 创建1个仓库对象(共享)
		List list = new ArrayList();
		
		// 创建两个线程对象
		// 生产者线程
		Thread thread1 = new Thread(new Producer(list));
		// 消费者线程
		Thread thread2 = new Thread(new Consumer(list));

		thread1.setName("生产者线程");
		thread2.setName("消费者线程");

		thread1.start();
		thread2.start();
	}
}

//生产线程
class Producer implements Runnable {
     
	
	// 仓库
	private List list;

	public Producer(List list) {
     
		this.list = list;
	}

	@Override
	public void run() {
     
		// 一直生产(使用死循环来模拟一直生产)
		while (true) {
     
			// 给仓库对象list加锁
			synchronized (list) {
     
				if (list.size() > 0) {
      // 大于0,说明仓库中已经有1个元素了
					try {
     
						// 当前线程进入等待状态,并且释放Producer之前占有的list集合的锁
						list.wait();
					} catch (InterruptedException e) {
     
						e.printStackTrace();
					}
				}
				// 程序能够执行到这里说明仓库是空的,可以生产
				Object obj = new Object();
				list.add(obj);
				System.out.println(Thread.currentThread().getName() + "----->" + obj);
				// 唤醒消费者进行消费
				list.notifyAll();
			}
		}
	}
}

//消费线程
class Consumer implements Runnable {
     
	
	// 仓库
	private List list;

	public Consumer(List list) {
     
		this.list = list;
	}

	@Override
	public void run() {
     
		// 一直消费
		while (true) {
     
			synchronized (list) {
     
				if (list.size() == 0) {
     
					try {
     
						// 仓库为空,消费者线程等待,释放掉list集合的锁
						list.wait();
					} catch (InterruptedException e) {
     
						e.printStackTrace();
					}
				}
				// 程序能够执行到此处说明仓库中有数据,进行消费
				Object obj = list.remove(0);
				System.out.println(Thread.currentThread().getName() + "----->" + obj);
				// 唤醒生产者生产
				list.notifyAll();
			}
		}
	}
}

你可能感兴趣的:(JavaSE学习路线,多线程,java)