线程的等待与通知
wait()函数
使用的时候要注意,要调用共享变量对象的wait()方法,必须先获取该对象的Monitor。否则会抛出IllegalMonitorStateException;
当调用一个共享变量的wait()方法时,该调用线程会被阻塞挂起,直到:1)其他线程调用了该共享变量的notify()或notifyAll()方法;2)其他线程调用了该线程的interrupt()方法,该线程抛出InterruptedException异常返回。
import java.util.PriorityQueue;
import com.tim.base.easystart.util.VeDate;
/**
* 1, wait使用之前需要获取到操作对象的Monitor
* 2, wait之后会释放锁定的Monitor
* 3, notifyAll会唤醒等待该对象的Monitor的所有线程,具体谁来获取到这个Monior是随机的
*/
public class WaitTest {
static PriorityQueue<String> queue=new PriorityQueue<String>();
public static void add(String content) throws InterruptedException {
synchronized (queue) {
while (queue.size() > 10) {
System.out.println("The Queue is Over 10...");
queue.wait();
}
queue.add(content);
queue.notifyAll();
}
}
public static String getQueue() {
synchronized (queue) {
while (queue.size() == 0) {
try {
System.out.println("The Queue is Empty...");
queue.wait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
String result = queue.poll();
queue.notifyAll();
return result;
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while(true) {
try {
add(VeDate.getNo(5));
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
while(true) {
System.out.println(getQueue());
}
}
});
t1.start();
t2.start();
}
}
//附上工具方法
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
public final class VeDate {
private VeDate() {
}
/**
* 根据用户传入的时间表示格式,返回当前时间的字符串
* @param sformat
* yyyyMMddhhmmss
* @return
*/
public static String getUserDate(String sformat) {
Date currentTime = new Date();
SimpleDateFormat formatter = new SimpleDateFormat(sformat);
String dateString = formatter.format(currentTime);
return dateString;
}
/**
* 生成格式为yyyymmddhhmmss+k位随机数
*
* @param k
* 表示是取几位随机数,可以自己定
* @return
*/
public static String getNo(int k) {
return getUserDate("yyyyMMddHHmmss") + getRandom(k);
}
/**
* 返回一个随机数
*
* @param i
* @return
*/
public static String getRandom(int i) {
Random jjj = new Random();
if (i == 0) {
return "";
}
StringBuffer jj = new StringBuffer();
for (int k = 0; k < i; k++) {
jj.append(jjj.nextInt(9));
}
return jj.toString();
}
}
wait()方法的常见使用方式:
synchronized (obj) {
whie(条件不满足) {
obj.wait();
}
}
不使用while来判断条件,可能会出现虚假唤醒,关于虚假唤醒可以点击查看wait()-虚假唤醒
当前线程调用共享变量的wait()方法之后,只会释放当前共享变量上的锁,如果当前线程还持有其他共享变量的锁,则这些锁是不会被释放的。
wait()函数调用后被打断
public class WaitInterrupt {
public static Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread run begin");
synchronized (obj) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("thread run end");
}
});
thread.start();
Thread.sleep(1000);
System.out.println("Interrupt start");
thread.interrupt();
System.out.println("Interrupt end");
}
}
wait(long timeout)和wait(long timeout, int nanos)
wait(long timeout)比wait()函数多了一个超时参数,如果一个线程调用共享对象的wait(long timeout)方法后,没有在timeout ms时间内,被其它线程调用该变量的notify()或notifyAll()方法唤醒,那么函数还是会因为超时而返回。
timeout设置为0效果等同于wait();
public final void wait() throws InterruptedException {
wait(0);
}
timeout设置小于0会抛出IllegalArgumentException异常
/**
* @param timeout the maximum time to wait in milliseconds.
* @throws IllegalArgumentException if the value of timeout is
* negative.
* @throws IllegalMonitorStateException if the current thread is not
* the owner of the object's monitor.
* @throws InterruptedException if any thread interrupted the
* current thread before or while the current thread
* was waiting for a notification. The <i>interrupted
* status</i> of the current thread is cleared when
* this exception is thrown.
* @see java.lang.Object#notify()
* @see java.lang.Object#notifyAll()
*/
public final native void wait(long timeout) throws InterruptedException;
wait(long timeout, int nanos)
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
notify()
一个线程调用共享对象的notify()方法之后,会唤醒一个在该共享变量上调用wait系列方法后被挂起的线程。如果有多个线程都调用wait方法,那么具体唤醒哪个线程是随机的。
被唤醒后不能立刻从wait方法返回并继续执行,它必须获取共享对象的监视器锁之后才可以返回。所以说唤醒它的线程释放了共享变量的监视器锁之后,被唤醒的线程也不一定会获取到共享对象的监视器锁,该线程必须和其它线程一起竞争这个锁,只有竞争到共享变量的监视器锁之后才可以继续执行。
调用该方法之前,该线程必须获取到共享对象的监视器锁,这个要求和wait()方法是一致的,否则会抛出InterruptedException异常。
notifyAll()
一个线程调用共享对象的notifyAll()方法之后,会唤醒所有由于调用该共享对象wait系列方法被阻塞的所有线程。
public class NotifyTest {
private static volatile Object resourceA = new Object();
public static void main(String[] args) throws InterruptedException {
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
System.out.println("threadA get resourceA lock");
try {
System.out.println("threadA begin wait");
resourceA.wait();
System.out.println("threadA end wait");
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
System.out.println("threadB get resourceA lock");
try {
System.out.println("threadB begin wait");
resourceA.wait();
System.out.println("threadB end wait");
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
System.out.println("threadC begin notify");
resourceA.notify();
}
}
});
//启动线程
threadA.start();
threadB.start();
Thread.sleep(1000); //等待线程A和B都执行到wait方法
threadC.start();
//等待线程结束
threadA.join();
threadB.join();
threadC.join();
System.out.println("main over");
}
}
输出:
threadB get resourceA lock
threadB begin wait
threadA get resourceA lock
threadA begin wait
threadC begin notify
threadB end wait
最终只有一个线程被唤醒,如果将resourceA.notify();修改为resourceA.notifyAll();
输出:
threadB get resourceA lock
threadB begin wait
threadA get resourceA lock
threadA begin wait
threadC begin notify
threadA end wait
threadB end wait
main over
从结果还可以理解下,线程A和B都被唤醒了,但是线程A先竞争到了resourceA的监视器锁,所以先执行完毕了,释放了监视器锁之后,线程B也拿到了这个Monitor,然后紧接着执行完毕,最终主线程执行完毕。
关于join方法我们后续再讨论。