Loading

Java 等待/通知机制

等待/通知机制,是指一个线程A调用了对象O的wait方法进入等待状态,而另一个线程B调用了对象O的notify方法或notifyAll方法,线程A收到通知后从对象O的wait方法返回,进而执行后续操作;

 

等待/通知的相关方法是任意Java对象都具备的,因为这些方法被定义在所有对象的超类java.lang.Object上;上述两个线程通过对象O来完成交互,而对象的wait方法和notify/notifyAll方法来完成等待方和通知方之间的交互;拥有相同锁的线程才能实现wait/notify机制;

 

wait方法是Object类的方法,它的作用是使当前执行wait方法的线程等待,在wait所在的代码行暂停执行,并释放锁,直到接收到通知或被中断为止;在调用wait方法之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步代码块中调用wait方法,通过通知机制使某个线程继续执行wait方法后面的代码时,对线程的选择是按照执行wait方法的顺序确定的,并需要重新获得锁;如果调用wait方法时没有持有适当的锁,则抛出IllegalMonitorStateException,它是RuntimeException的一个子类,因此不需要try-catch语句捕获异常;

 

  • wait方法立即释放锁
public class WaitReleaseLock {
	public void testMethod(Object lock) {
		try {
			synchronized (lock) {
				System.out.println("begin wait");
				lock.wait();
				System.out.println("end wait");
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		Object lock = new Object();

		MyThread t1 = new MyThread(lock);
		t1.start();

		MyThread t2 = new MyThread(lock);
		t2.start();
	}
}

class MyThread extends Thread {
	private Object lock;

	public MyThread(Object lock) {
		super();
		this.lock = lock;
	}

	@Override
	public void run() {
		WaitReleaseLock waitReleaseLock = new WaitReleaseLock();
		waitReleaseLock.testMethod(lock);
	}
}

 

  • sleep方法不释放锁

  将上面的testMehod方法修改如下:

public void testMethod(Object lock) {
	try {
		synchronized (lock) {
			System.out.println("begin wait --- " + Thread.currentThread());
//				lock.wait();
			Thread.sleep(4000);
			System.out.println("end wait --- " + Thread.currentThread());
		}
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
}

  

  执行是同步效果,因为sleep方法不释放锁;

 

调用sleep方法的线程不会失去对象监视器的所有权;

 

notify方法要在同步方法或同步代码块中调用,即在调用前,线程必须获得锁,如果调用notify方法时没有持有适当的锁,则会抛出IllegalMonitorStateException;该方法用来通知那些可能等待该锁的其他线程,如果有多个线程等待,则按照执行wait方法的顺序对处于wait状态的线程发出一次通知,并使该线程重新获得锁;需要说明的是,执行notify方法后,当前线程不会马上释放该锁,wait状态的线程也不能马上获取该对象锁,要等到执行notify方法的线程将程序执行完,也就是退出synchronized同步区域后,当前线程才会释放锁,而wait状态的线程才可以获取该对象锁;当第一个获得该对象锁的wait状态的线程运行完毕后,它会释放该对象锁,此时如果没有再次使用notify方法,那么其他wait状态的线程因为没有得到通知,会继续处于wait状态;

  • notify方法不立即释放锁
public class NotifyHoldLock {

	private Object lock = new Object();

	public void waitMethod() {
		try {
			synchronized (lock) {
				System.out.println("begin wait -- " + Thread.currentThread() + " -- " + System.currentTimeMillis());
				lock.wait();
				System.out.println("end wait -- " + Thread.currentThread() + " -- " + System.currentTimeMillis());
			}
		}
		catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public void notifyMethod() {
		try {
			synchronized (lock) {
				System.out.println("begin notify -- " + Thread.currentThread() + " -- " + System.currentTimeMillis());
				lock.notify();
				Thread.sleep(5000);
				System.out.println("end notify -- " + Thread.currentThread() + " -- " + System.currentTimeMillis());
			}
		}
		catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) throws InterruptedException {
		NotifyHoldLock notifyHoldLock = new NotifyHoldLock();

		MyThreadA myThreadA = new MyThreadA(notifyHoldLock);
		myThreadA.start();

		Thread.sleep(100);

		MyThreadB myThreadB = new MyThreadB(notifyHoldLock);
		myThreadB.start();
	}

	static class MyThreadA extends Thread {
		private NotifyHoldLock notifyHoldLock;

		public MyThreadA(NotifyHoldLock notifyHoldLock) {
			this.notifyHoldLock = notifyHoldLock;
		}

		@Override
		public void run() {
			notifyHoldLock.waitMethod();
		}
	}

	static class MyThreadB extends Thread {
		private NotifyHoldLock notifyHoldLock;

		public MyThreadB(NotifyHoldLock notifyHoldLock) {
			this.notifyHoldLock = notifyHoldLock;
		}

		@Override
		public void run() {
			notifyHoldLock.notifyMethod();
		}
	}
}

  

 

   通过对控制台输出的时间分析,可以得出:必须执行完notify方法所在的同步代码块后才能释放锁;

 

  • interrupt方法遇到wait方法

  当线程调用wait方法后,再对线程对象执行interrupt方法会出现InterruptException异常;

public class WaitInterruptException {
	public void testMethod(Object lock) {
		try {
			synchronized (lock) {
				System.out.println("begin wait");
				lock.wait();
				System.out.println("end wait");
			}
		}
		catch (InterruptedException e) {
			e.printStackTrace();
			System.out.println("出异常,wait的线程被interrupt..");
		}
	}

	static class MyThread extends Thread {
		private Object lock;

		public MyThread(Object lock) {
			this.lock = lock;
		}

		@Override
		public void run() {
			WaitInterruptException waitInterruptException = new WaitInterruptException();
			waitInterruptException.testMethod(lock);
		}
	}

	public static void main(String[] args) {

		try {
			Object lock = new Object();

			MyThread myThread = new MyThread(lock);
			myThread.start();

			Thread.sleep(5000);
			myThread.interrupt();
		}
		catch (InterruptedException e) {
			e.printStackTrace();
		}

	}
}

  

  

 

  从上面几个示例可以整理如下:

  1.  执行完notify方法后,按照执行wait方法的顺序唤醒其他线程,notify方法所在的同步代码块执行完才会释放对象的锁,其他线程继续执行wait方法后面的代码;
  2. 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放;
  3. 在执行同步代码块的过程中,执行了锁所属的对象的wait方法,这个线程会释放对象锁,等待被唤醒;

 

等待方遵循如下原则:

1.获取对象的锁

2.如果条件不满足,那么调用对象的wait()方法,被通知后任要检查条件

3.条件满足则进行对于的逻辑

 

伪代码如下:

 synchronized(对象) {
     while (条件不满足) {
         对象.wait();
     }
     
     对应的逻辑;
 }

  

等待/通知的目的是确保等待线程从wait()方法返回时能够感知到通知线程对变量所做出的的修改;

 

通知方遵循如下原则:

1.获得对象的锁

2.改变条件

3.通知所有等待在对象上的线程

 

对应的伪代码如下:

 synchronized(对象) {
     改变条件
     对象.notifyAll();
 }

 

需要注意

1.使用wait(),notify()和notifyAll()时需要先调用对象加锁

2.使用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列

3.notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifyAll()的线程释放锁之后,等待线程才会有机会从wait()返回

4.notify()方法将等待队列中的一个等待线程从等待队列中移动到同步队列,而notifyAll()方法则时将等待队列中所有的线程全部移到同步队列,被移动的线程的状态由WAITING变为BLOCKED;

5.从wait()方法返回的前提是获得了所有调用对象的锁

 

等待超时模式

假设超时时间段时T,那么可以推断在当前时间 now + T之后就好超时;

定义如下变量:

  • 等待持续时间:REMAINING = T

  • 超时时间:FUTURE = now + T

这时仅需要wait(REMAINING) 即可,在wait(REMAINING)返回之后将会执行:REMAINING = FUTURE - now;如果REMAINING 小于等于0,表示已经超时,直接退出,否则将继续执行wait(REMAINING);

 

伪代码如下:

 public synchronized Object get(long mills) throw InterruptedException {
     long future = System.currentTimeMillis() + mills;
     long remaining = mills;
     
     //当超时大于0并且result返回值不满足要求
     while ((result == null) && remaining > 0) {
         wait(remaining);
         remaining = future - System.currentTimeMillis();
     }
     
     return result;
 }

  

  

 

posted @ 2020-03-13 11:11  街头卖艺的肖邦  阅读(589)  评论(0编辑  收藏  举报