本文主要学习 JAVA 多线程中的 wait() 方法 与 notify()/notifyAll() 方法的用法。
①wait() 与 notify/notifyAll 方法必须在同步代码块中使用
②wait() 与 notify/notifyAll() 的执行过程
③中断 调用 wait() 方法进入等待队列的 线程
④notify 通知的顺序不能错
⑤多线程中测试某个条件的变化用 if 还是用 while?
①wait() 与 notify/notifyAll 方法必须在同步代码块中使用
wait() 与 notify/notifyAll() 是 Object 类的方法,在执行两个方法时,要先获得锁。那么怎么获得锁呢?
在这篇:文章中介绍了使用 synchronized 关键字获得锁。因此,wait() 与 notify/notifyAll() 经常与 synchronized 搭配使用,即在 synchronized 修饰的同步代码块或方法里面调用 wait() 与 notify/notifyAll() 方法。
②wait() 与 notify/notifyAll() 的执行过程
由于 wait() 与 notify/notifyAll() 是放在同步代码块中的,因此线程在执行它们时,肯定是进入了临界区中的,即该线程肯定是获得了锁的。
当线程执行 wait() 时,会把当前的锁释放,然后让出 CPU,进入等待状态。
当执行 notify/notifyAll 方法时,会唤醒一个处于等待该 对象锁 的线程,然后继续往下执行,直到执行完退出对象锁锁住的区域(synchronized 修饰的代码块)后再释放锁。
从这里可以看出,notify/notifyAll() 执行后,并不立即释放锁,而是要等到执行完临界区中代码后,再释放。故,在实际编程中,我们应该尽量在线程调用 notify/notifyAll() 后,立即退出临界区。即不要在 notify/notifyAll() 后面再写一些耗时的代码。
示例如下:(摘自《JAVA 多线程核心技术》)
- 1 public class Service {
- 2 3 public void testMethod(Object lock) {
- 4
- try {
- 5 synchronized(lock) {
- 6 System.out.println("begin wait() ThreadName="7 + Thread.currentThread().getName());
- 8 lock.wait();
- 9 System.out.println(" end wait() ThreadName="10 + Thread.currentThread().getName());
- 11
- }
- 12
- } catch(InterruptedException e) {
- 13 e.printStackTrace();
- 14
- }
- 15
- }
- 16 17 public void synNotifyMethod(Object lock) {
- 18
- try {
- 19 synchronized(lock) {
- 20 System.out.println("begin notify() ThreadName="21 + Thread.currentThread().getName() + " time="22 + System.currentTimeMillis());
- 23 lock.notify();
- 24 Thread.sleep(5000);
- 25 System.out.println(" end notify() ThreadName="26 + Thread.currentThread().getName() + " time="27 + System.currentTimeMillis());
- 28
- }
- 29
- } catch(InterruptedException e) {
- 30 e.printStackTrace();
- 31
- }
- 32
- }
- 33
- }
在第 3 行的 testMethod() 中调用 wait(),在第 17 行的 synNotifyMethod() 中调用 notify()
从上面的代码可以看出,wait() 与 notify/notifyAll() 都是放在同步代码块中才能够执行的。如果在执行 wait() 与 notify/notifyAll() 之前没有获得相应的对象锁,就会抛出:java.lang.IllegalMonitorStateException 异常。
在第 8 行,当 ThreadA 线程执行 lock.wait(); 这条语句时,释放获得的对象锁 lock,并放弃 CPU,进入等待队列。
当另一个线程执行第 23 行 lock.notify();,会唤醒 ThreadA,但是此时它并不立即释放锁,接下来它睡眠了 5 秒钟 (sleep() 是不释放锁的,事实上 sleep()也可以不在同步代码块中调用),直到第 28 行,退出 synchronized 修饰的临界区时,才会把锁释放。这时,ThreadA 就有机会获得另一个线程释放的锁,并从等待的地方起(第 24 行)起开始执行。
接下来是两个线程类,线程类 ThreadA 调用 testMethod() 方法执行 lock.wait(); 时被挂起,另一个线程类 synNotifyMethodThread 调用 synNotifyMethod() 负责唤醒挂起的线程。代码如下:
- 1 public class ThreadA extends Thread {
- 2 private Object lock;
- 3
- 4 public ThreadA(Object lock) {
- 5 super();
- 6 this.lock = lock;
- 7 }
- 8
- 9 @Override
- 10 public void run() {
- 11 Service service = new Service();
- 12 service.testMethod(lock);
- 13 }
- 14 }
- 15
- 16 public class synNotifyMethodThread extends Thread {
- 17 private Object lock;
- 18
- 19 public synNotifyMethodThread(Object lock) {
- 20 super();
- 21 this.lock = lock;
- 22 }
- 23
- 24 @Override
- 25 public void run() {
- 26 Service service = new Service();
- 27 service.synNotifyMethod(lock);
- 28 }
- 29 }
再接下来是测试类:
- 1 public class Test {
- 2
- 3 public static void main(String[] args) throws InterruptedException {
- 4
- 5 Object lock = new Object();
- 6
- 7 ThreadA a = new ThreadA(lock);
- 8 a.start();
- 9
- 10 NotifyThread notifyThread = new NotifyThread(lock);
- 11 notifyThread.start();
- 12
- 13 synNotifyMethodThread c = new synNotifyMethodThread(lock);
- 14 c.start();
- 15 }
- 16 }
③中断 调用 wait() 方法进入等待队列的 线程
示例代码如下:
- 1 public class Service {
- 2 3 public void testMethod(Object lock) {
- 4
- try {
- 5 synchronized(lock) {
- 6 System.out.println("begin wait()");
- 7 lock.wait();
- 8 System.out.println(" end wait()");
- 9
- }
- 10
- } catch(InterruptedException e) {
- 11 e.printStackTrace();
- 12 System.out.println("出现异常");
- 13
- }
- 14
- }
- 15
- }
- 16 17 public class ThreadA extends Thread {
- 18 19 private Object lock;
- 20 21 public ThreadA(Object lock) {
- 22 super();
- 23 this.lock = lock;
- 24
- }
- 25 26@Override 27 public void run() {
- 28 Service service = new Service();
- 29 service.testMethod(lock);
- 30
- }
- 31
- }
注意,在第 23 行 wait() 方法是 Object 类的对象 lock 调用的。而下面的 interrupt() 方法是 ThreadA 类的对象调用的。在 ThreadA 里面,将 Object 的对象作为参数传给了 testMethod() 方法,ThreadA 的 run() 方法去调用 testMethod(),从而 wait() 使 ThreadA 的线程暂停了(暂停当前执行 wait() 的线程)。从这里可以看出一个区别:
Object 类中与线程有关的方法:
1)notify/notifyAll
2)wait()/wait(long)
java.lang.Thread 中与之相关的方法:
1)interrupt()
2)sleep()/sleep(long)
3)join()/suspend()/resume()....
测试类代码如下:
- 1 public class Test {
- 2
- 3 public static void main(String[] args) {
- 4
- 5 try {
- 6 Object lock = new Object();
- 7
- 8 ThreadA a = new ThreadA(lock);
- 9 a.start();
- 10
- 11 Thread.sleep(5000);
- 12
- 13 a.interrupt();
- 14 } catch (InterruptedException e) {
- 15 e.printStackTrace();
- 16 }
- 17 }
- 18 }
当执行第 13 行的 interrupt() 时,处于 wait 中的线程" 立即 " 被唤醒(一般是),并抛出异常。此时,线程也就结束了。
④notify 通知的顺序不能错
假设在线程 A 中执行 wait(),在线程 B 中执行 notify()。但如果线程 B 先执行了 notify() 然后结束了,线程 A 才去执行 wait(),那此时,线程 A 将无法被正常唤醒了(还可以通过③中提到的 interrupt() 方法以抛出异常的方式唤醒 ^~^)。
中的第③点提到了 notify 通知顺序出错会导致 调用 wait() 进入等待队列的线程再也无法被唤醒了。
⑤多线程中测试某个条件的变化用 if 还是用 while?
以前一直不明白 当在线程的 run() 方法中需要测试某个条件时,为什么用 while,而不用 if??? 直到看到了这个简单的例子,终于明白了。。。。
这个例子是这样的:
有两个线程从 List 中删除数据,而只有一个线程向 List 中添加数据。初始时,List 为空,只有往 List 中添加了数据之后,才能删除 List 中的数据。添加数据的线程向 List 添加完数据后,调用 notifyAll(),唤醒了两个删除线程,但是它只添加了一个数据,而现在有两个唤醒的删除线程,这时怎么办?
如果用 if 测试 List 中的数据的个数,则会出现 IndexOutofBoundException,越界异常。原因是,List 中只有一个数据,第一个删除线程把数据删除后,第二个线程再去执行删除操作时,删除失败,从而抛出 IndexOutofBoundException。
但是如果用 while 测试 List 中数据的个数,则不会出现越界异常!!!神奇。
当 wait 等待的条件发生变化时,会造成程序的逻辑混乱 --- 即,List 中没有数据了,再还是有线程去执行删除数据的操作。因此,需要用 while 循环来判断条件的变化,而不是用 if。
示例如下:摘自《JAVA 多线程编程核心技术》
Add 类,负责添加数据:
- public class Add {
- private String lock;
- public Add(String lock) {
- super();
- this.lock = lock;
- }
- public void add() {
- synchronized (lock) {
- ValueObject.list.add("anyString");
- lock.notifyAll();
- }
- }
- }
- public class ThreadAdd extends Thread {
- private Add p;
- public ThreadAdd(Add p) {
- super();
- this.p = p;
- }
- @Override
- public void run() {
- p.add();
- }
- }
Subtract 类,负责删除数据 ---- 先要进行条件判断,然后执行 wait(),这意味着:wait 等待的条件可能发生变化!!!
- public class Subtract {
- private String lock;
- public Subtract(String lock) {
- super();
- this.lock = lock;
- }
- public void subtract() {
- try {
- synchronized (lock) {
- if(ValueObject.list.size() == 0) {//将这里的if改成while即可保证不出现越界异常!!!!
- System.out.println("wait begin ThreadName="
- + Thread.currentThread().getName());
- lock.wait();
- System.out.println("wait end ThreadName="
- + Thread.currentThread().getName());
- }
- ValueObject.list.remove(0);
- System.out.println("list size=" + ValueObject.list.size());
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- public class ThreadSubtract extends Thread {
- private Subtract r;
- public ThreadSubtract(Subtract r) {
- super();
- this.r = r;
- }
- @Override
- public void run() {
- r.subtract();
- }
- }
封装的 List 队列:
- public class ValueObject {
- public static List list = new ArrayList();
- }
测试类:
- public class Run {
- public static void main(String[] args) throws InterruptedException {
- String lock = new String("");
- Add add = new Add(lock);
- Subtract subtract = new Subtract(lock);
- ThreadSubtract subtract1Thread = new ThreadSubtract(subtract);
- subtract1Thread.setName("subtract1Thread");
- subtract1Thread.start();
- ThreadSubtract subtract2Thread = new ThreadSubtract(subtract);
- subtract2Thread.setName("subtract2Thread");
- subtract2Thread.start();
- Thread.sleep(1000);
- ThreadAdd addThread = new ThreadAdd(add);
- addThread.setName("addThread");
- addThread.start();
- }
- }
来源: http://www.bubuko.com/infodetail-1989956.html