Java—wait和notify的使用

1,226 阅读2分钟

wait和notify的使用

规则

  1. wait和notify都只能在synchronized代码块里面调用
  2. synchronized(object)的语义是只有持有object锁的对象才能进入,同一时间只能有一个线程持有该锁
  3. wait和notify的调用者最好是final修饰的对象,否则容易出现对象被改变之后的IllegalMonitorStateException错误
  4. 进入了synchronized代码块代表获取了该对象的锁
  5. 调用object.wait代表暂时释放object锁并处于阻塞状态,这时,另外的线程就可以进入synchronized代码块了
  6. 调用object.notify会唤醒之前调用了object.wait的(也就是处于阻塞状态的)线程,但是锁的释放要等到当前的同步代码块执行完毕才释放
  7. synchronized代码块里,object.notify之后的代码会继续执行,并且能够使用object对象

使用

import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author mingC
 * @date 2018/7/7
 */
public class WaitAndNotify {
	final Object lock = new Object();
	CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

	class Consumer implements Runnable {

		@Override
		public void run() {
			synchronized (lock) {
				System.out.println(Thread.currentThread().getName() + "进入同步块");
				System.out.println(Thread.currentThread().getName() + "wait()");
				try {
					lock.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "唤醒..");
			}
		}
	}

	class Producer implements Runnable {

		@Override
		public void run() {
			synchronized (lock) {
				System.out.println(Thread.currentThread().getName() + "进入同步块");
				System.out.println(Thread.currentThread().getName() + "notify()");
				lock.notify();
				System.out.println(lock);
				System.out.println("测试notify调用后,后面的代码是否还执行");
			}
		}
	}

	void start() {
		ExecutorService executor = Executors.newCachedThreadPool();
		executor.execute(new Consumer());
		executor.execute(new Producer());
	}

	public static void main(String[] args) {
		new WaitAndNotify().start();
	}
}

输出

pool-1-thread-1进入同步块
pool-1-thread-1wait()
pool-1-thread-2进入同步块
pool-1-thread-2notify()
java.lang.Object@4608042a
测试notify调用后,后面的代码是否还执行
pool-1-thread-1唤醒..

生产者消费者

合理使用wait和notify可以避免浪费cpu资源

比如生产者消费者问题,当产品数目为0时,消费者线程应当进入等待状态,避免用while(true)不断地访问,否则会浪费cpu资源。

示例 这里只考虑了单个生产者和单个消费者。

import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author mingC
 * @date 2018/7/7
 */
public class WaitAndNotify {
	final Object readLock = new Object();  //读锁
	CopyOnWriteArrayList<String> productList = new CopyOnWriteArrayList<>();  //产品列表

	class Consumer implements Runnable {

		@Override
		public void run() {
			synchronized (readLock) {
				while (true) {
					//如果当前产品数为0,则不能读,进入阻塞状态
					if (productList.size() == 0) {
						try {
							System.out.println("产品数为0,消费者阻塞..");
							readLock.wait();
							System.out.println("消费者唤醒");
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
					//读,这里可以保证产品数不为0
					String oldValue = productList.remove(0);
					System.out.println("消费产品:" + oldValue);
				}
			}
		}
	}

	class Producer implements Runnable {

		@Override
		public void run() {
			int count = 0;
			while (true) {
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				//5秒生产一件产品,模拟读取速度远大于生产速度
				productList.add("产品" + count);
				//唤醒消费者
				if (productList.size() == 1) {
					synchronized (readLock) {
						readLock.notify();
					}
				}
				count++;
			}
		}
	}

	void start() {
		ExecutorService executor = Executors.newCachedThreadPool();
		executor.execute(new Consumer());
		executor.execute(new Producer());
	}

	public static void main(String[] args) {
		new WaitAndNotify().start();
	}
}

输出:

产品数为0,消费者阻塞..
消费者唤醒
消费产品:产品0
产品数为0,消费者阻塞..
消费者唤醒
消费产品:产品1
产品数为0,消费者阻塞..