Java中的线程等待和唤醒

2019-09-27  

用死循环的方式,一直尝试获取锁。在任务耗时比较长或者并发冲突比较大的时候,就不适合。

因为任务耗时比较长或者并发冲突比较大的时候,可能要循环上万次都不止,才能获取到锁,太消耗 CPU 。

 

这种场景下,理想的解决方案:

  • 线程执行任务的条件不满足时,阻塞自己,进入等待状态;当线程执行的任务条件满足时,通知等待的线程继续执行。
  • 线程阻塞的方式,能够避免循环等待对 CPU 的消耗。

 

在 Java 中实现线程等待和唤醒,有多种方式,比较常见的有以下两种:

1、synchronized 加 wait()、notify()、notifyAll()

  • 调用 wait() 方法后,当前线程就会被阻塞,进入等待队列中,释放持有的互斥锁
  • 条件满足时调用 notify(),会通知等待队列中的某个线程,告诉它条件满足可以继续执行
  • 条件满足时调用 notifyAll(),会通知等待队列中的所有线程,告诉它们条件满足可以继续执行
  • 只能在 synchronized 加锁的对象上调用 wait() 、notify()、notifyAll() 方法,否则报 java.lang.IllegalMonitorStateException

 

看下这个例子

package constxiong.concurrency.a025;

import java.util.HashSet;
import java.util.Set;

/**
 * 测试 线程等待/唤醒
 * @author ConstXiong
 * @date 2019-09-27 15:44:48
 */
public class TestWaitAndNotify {

	//单例的资源管理类
	private final static MangerWaitAndNotify manager = new MangerWaitAndNotify();
	
	//资源1
	private static Object res1 = new Object();
	
	//资源2
	private static Object res2 = new Object();
	
	public static void main(String[] args) {
		new Thread(() -> {
			manager.applyResources(res1, res2);
			//向管理类,申请res1和res2,申请失败,重试
			try {
				System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源成功");
				synchronized (res1) {
					System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res1 资源的锁");
					//休眠 3 秒
					try {
						Thread.sleep(3000);
					} catch (Exception e) {
						e.printStackTrace();
					}
					synchronized (res2) {
						System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res2 资源的锁");
					}
				}
			} finally {
				manager.returnResources(res1, res2);//归还资源
			}
		}).start();
		
		new Thread(() -> {
			manager.applyResources(res1, res2);
			try {
				System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源成功");
				synchronized (res2) {
					System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res2 资源的锁");
					//休眠 1秒
					try {
						Thread.sleep(1000);
					} catch (Exception e) {
						e.printStackTrace();
					}
					synchronized (res1) {
						System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res1 资源的锁");
					}
				}
			} finally {
				manager.returnResources(res1, res2);//归还资源
			}
		}).start();
		
	}
	
}

/**
 * 资源申请、归还管理类
 * @author ConstXiong
 * @date 2019-09-24 14:10:57
 */
class MangerWaitAndNotify {
	
	//资源存放集合
	private Set<Object> resources = new HashSet<Object>();
	
	/**
	 * 申请资源
	 * @param res1
	 * @param res2
	 * @return
	 */
	synchronized void applyResources(Object res1, Object res2) {
		while (resources.contains(res1) || resources.contains(res1)) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		resources.add(res1);
		resources.add(res2);
	}
	
	/**
	 * 归还资源
	 * @param res1
	 * @param res2
	 */
	synchronized void returnResources(Object res1, Object res2) {
		resources.remove(res1);
		resources.remove(res2);
		notifyAll();
	}
	
}

 

打印结果

线程:Thread-0 申请 res1、res2 资源成功
线程:Thread-0 获取到 res1 资源的锁
线程:Thread-0 获取到 res2 资源的锁
线程:Thread-1 申请 res1、res2 资源成功
线程:Thread-1 获取到 res2 资源的锁
线程:Thread-1 获取到 res1 资源的锁

 

 

2、Condition、await()、signal()、signalAll()

  • Condition 对象是通过 Lock 对象的 newCondition() 方法获得
  • 调用 await() 方法后,当前线程就会被阻塞,进入等待队列
  • 条件满足时调用 signal(),会通知等待队列中的某个线程,告诉它条件满足可以继续执行
  • 条件满足时调用 signalAll(),会通知等待队列中的所有线程,告诉它们条件满足可以继续执行
  • 只能在调用 Lock 对象 lock()方法后,调用 Condition 对象的 wait() 、signal()、signalAll() 方法,否则报 java.lang.IllegalMonitorStateException

 

示例代码

package constxiong.concurrency.a025;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 测试 线程等待/唤醒
 * @author ConstXiong
 * @date 2019-09-27 15:44:48
 */
public class TestAwaitAndSignal {

	//单例的资源管理类
	private final static MangerAwaitAndNotify manager = new MangerAwaitAndNotify();
	
	//资源1
	private static Object res1 = new Object();
	
	//资源2
	private static Object res2 = new Object();
	
	public static void main(String[] args) {
		new Thread(() -> {
			manager.applyResources(res1, res2);
			//向管理类,申请res1和res2,申请失败,重试
			try {
				System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源成功");
				synchronized (res1) {
					System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res1 资源的锁");
					//休眠 3 秒
					try {
						Thread.sleep(3000);
					} catch (Exception e) {
						e.printStackTrace();
					}
					synchronized (res2) {
						System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res2 资源的锁");
					}
				}
			} finally {
				manager.returnResources(res1, res2);//归还资源
			}
		}).start();
		
		new Thread(() -> {
			manager.applyResources(res1, res2);
			try {
				System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源成功");
				synchronized (res2) {
					System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res2 资源的锁");
					//休眠 1秒
					try {
						Thread.sleep(1000);
					} catch (Exception e) {
						e.printStackTrace();
					}
					synchronized (res1) {
						System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res1 资源的锁");
					}
				}
			} finally {
				manager.returnResources(res1, res2);//归还资源
			}
		}).start();
		
	}
	
}

/**
 * 资源申请、归还管理类
 * @author ConstXiong
 * @date 2019-09-24 14:10:57
 */
class MangerAwaitAndNotify {
	
	//资源存放集合
	private Set<Object> resources = new HashSet<Object>();
	
	private final Lock lock = new ReentrantLock();
	private final Condition condition = lock.newCondition();
	
	
	/**
	 * 申请资源
	 * @param res1
	 * @param res2
	 * @return
	 */
	void applyResources(Object res1, Object res2) {
		while (resources.contains(res1) || resources.contains(res1)) {
			lock.lock();
			try {
				condition.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
		}
		resources.add(res1);
		resources.add(res2);
	}
	
	/**
	 * 归还资源
	 * @param res1
	 * @param res2
	 */
	void returnResources(Object res1, Object res2) {
		lock.lock(); 
		try {
			resources.remove(res1);
			resources.remove(res2);
			condition.signalAll();
		} finally {
			lock.unlock();
		}
	}
	
}

 

打印结果

线程:Thread-0 申请 res1、res2 资源成功
线程:Thread-0 获取到 res1 资源的锁
线程:Thread-0 获取到 res2 资源的锁
线程:Thread-1 申请 res1、res2 资源成功
线程:Thread-1 获取到 res2 资源的锁
线程:Thread-1 获取到 res1 资源的锁

 

ConstXiong 备案号:苏ICP备16009629号-3