目录
由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知.
但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序.
完成这个协调工作, 主要涉及到三个方法
wait() / wait(longtimeout): 让当前线程进入等待状态.
notify() / notifyAll(): 唤醒在当前对象上等待的线程.
注意: wait, notify, notifyAll 都是 Object 类的方法.
1.wait() 方法
wait 做的事情:
1.使当前执行代码的线程进行等待. (把线程放到等待队列中)
2.释放当前的锁
3.满足一定条件时被唤醒, 重新尝试获取这个锁.
I wait 要搭配 synchronized 来使用. 脱离 synchronized 使用 wait 会直接抛出异常.
wait 结束等待的条件:
1.其他线程调用该对象的 notify 方法.
2.wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).
3.其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常.
代码示例: 观察wait()方法的使用
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
synchronized (object) {
System.out.println("等待中");
object.wait();
System.out.println("等待结束");
}
}
这样在执行到object.wait()之后就一直等待下去,那么程序肯定不能一直这么等待下去了。这个时候就 需要使用到了另外一个方法唤醒的方法notify()。
2.notify()方法
notify 方法是唤醒等待的线程.
方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 "先来后到")
在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。
代码示例: 使用notify()方法唤醒线程
创建线程t1, 在run 内部循环调用 wait.
创建线程t2, 在 run 内部循环调用notify
注意:t1和 t2内部持有同一个 Object locker. WaitTask 和 NotifyTask 要想配合就需要搭配同一个 Object.
package thread;
// wait 和 notify
public class ThreadDemo {
// 使用这个锁对象来负责加锁, wait, notify
private static final Object locker = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
synchronized (locker) {
System.out.println("t1 wait 开始");
try {
locker.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 wait 结束");
}
}
});
t1.start();
Thread t2 = new Thread(() -> {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (locker) {
System.out.println("t2 notify 开始");
locker.notify();
System.out.println("t2 notify 结束");
}
}
});
t2.start();
}
}
代码运行结果:
3.notifyAll()方法
notify方法只是唤醒某一个等待线程. 使用notifyAll方法可以一次唤醒所有的等待线程.
代码范例:
在上面的代码基础上进行修改,创建三个休眠线程,一个唤醒线程,使用notify方法进行测试:
package thread;
// wait 和 notify
public class ThreadDemo {
// 使用这个锁对象来负责加锁, wait, notify
private static final Object locker = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
synchronized (locker) {
System.out.println("t1 wait 开始");
try {
locker.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 wait 结束");
}
}
});
t1.start();
Thread t2 = new Thread(() -> {
while (true) {
synchronized (locker) {
System.out.println("t2 wait 开始");
try {
locker.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2 wait 结束");
}
}
});
t2.start();
Thread t3 = new Thread(() -> {
while (true) {
synchronized (locker) {
System.out.println("t3 wait 开始");
try {
locker.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t3 wait 结束");
}
}
});
t3.start();
Thread t4 = new Thread(() -> {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (locker) {
System.out.println("t4 notify 开始");
locker.notify();
System.out.println("t4 notify 结束");
}
}
});
t4.start();
}
}
运行结果:
此时可以看到,调用notify只能唤醒一个线程
修改唤醒线程的run方法,将notify换成notifyAll方法进行测试:
此时可以看到,调用notifyAll方法可以同时唤醒3个wait中的线程
注意:
虽然是同时唤醒3个线程,但是这3个线程需要竞争锁,所以并不是同时执行,而是仍然有先有后的执行。
4.理解notify和notifyAll
notify只能唤醒等待队列中的一个线程,其他线程还是乖乖等着
notifyAll一下全都唤醒,需要这些线程重新竞争锁
5.wait和sleep的对比
1、 sleep 来自 Thread 类,和 wait 来自 Object 类。
2、最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
3、wait,notify和 notifyAll 只能在同步控制方法或者同步控制块里面使用,而 sleep 可以在任何地方使用(使用范围)
4、 sleep 必须捕获异常,而 wait ,notify 和 notifyAll 不需要捕获异常
(1) sleep 方法属于 Thread 类中方法,表示让一个线程进入睡眠状态,等待一定的时间之后,自动醒来进入到可运行状态,不会马上进入运行状态,因为线程调度机制恢复线程的运行也需要时间,一个线程对象调用了 sleep方法之后,并不会释放他所持有的所有对象锁,所以也就不会影响其他进程对象的运行。但在 sleep 的过程中过程中有可能被其他对象调用它的 interrupt() ,产生 InterruptedException 异常,如果你的程序不捕获这个异常,线程就会异常终止,进入 TERMINATED 状态,如果你的程序捕获了这个异常,那么程序就会继续执行catch语句块(可能还有 finally 语句块)以及以后的代码。
注意 sleep() 方法是一个静态方法,也就是说他只对当前对象有效,通过 t.sleep() 让t对象进入 sleep ,这样的做法是错误的,它只会是使当前线程被 sleep 而不是 t 线程
(2) wait 属于 Object 的成员方法,一旦一个对象调用了wait方法,必须要采用 notify() 和 notifyAll() 方法唤醒该进程;如果线程拥有某个或某些对象的同步锁,那么在调用了 wait() 后,这个线程就会释放它持有的所有同步资源,而不限于这个被调用了 wait() 方法的对象。 wait() 方法也同样会在 wait 的过程中有可能被其他对象调用 interrupt() 方法而产生