文章目录

  • wait() 一定需要notify()唤醒吗?
  • 正常情况
  • 线程对象为锁资源时


wait() 一定需要notify()唤醒吗?

本质上都是通过notify()(通知)来唤醒的。也就是说等待的线程一定要被通知才会醒的!只是处罚的途径不同。

一般也就两种情况:

  • 正常情况下的等待、唤醒,是要用notify()来唤醒的。
  • 当线程执行完之后,被等待的线程会在退出前调用notifyAll()方法通知所有的等待线程继续执行。

《下面内容摘抄自《实战Java高并发程序设计2》2.2节。》

正常情况

正常情况很简单,也就是wait()与notify()方法的使用。关于两个方法的详解可看:线程的基本操作(全)

直接看案例:

线程T1调用临界区资源OBJECT的wait()方法,进入这个资源对象的等待队列中,等待唤醒

线程T2调用临界区资源OBJECT的notify()方法,唤醒了线程T1。

package com.wlw.test;

public class testThread {
    // 临界区资源
    public static final Object OBJECT = new Object();

    public static class T1 extends Thread {

        @Override
        public void run() {
            // 锁
            synchronized (OBJECT) {
                System.out.println("T1 start.");
                try {
                    System.out.println("T1 wait for object");
                    // 等待
                    OBJECT.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("T1 end.");
            }
        }
    }

    public static class T2 extends Thread {
        @Override
        public void run() {
            // 锁
            synchronized (OBJECT) {
                System.out.println("T2 start.");
                // 唤醒
                OBJECT.notify();
                System.out.println("T2 notify for object");
                try {
                    // 模拟代码执行,休眠1s
                    Thread.sleep(1000);
                    System.out.println("T2 sleep 1s.");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("T2 end.");
            }
        }
    }

    public static void main(String[] args) {
        T1 t1 = new T1();
        T2 t2 = new T2();
        t1.start();
        t2.start();
    }
}

执行结果:

T1 start.
T1 wait for object
T2 start.
T2 notify for object
T2 sleep 1s.
T2 end.
T1 end.

线程对象为锁资源时

这里用到了Thread.join()方法。join()方法为加入的意思。在A线程中执行B线程的join()方法,A会等待B执行完之后,再继续执行。

看案例:

package com.wlw.test;

public class testThread {

    public volatile static int i = 0;
    
    public static class T1 extends Thread {

        @Override
        public void run() {
            for (i = 0; i < 100000; i++) {
            }
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        T1 t1 = new T1();
        t1.start();
      	// 等待t1线程执行完
        t1.join();
        System.out.println(i);
    }
}

在主函数中,如果不使用join()方法等待T1线程,那么得到的i很可能是0或者一个非常小的数字。因为T1还没开始执行,i的值就已经被输出了。但在使用join()方法后,表示主线程愿意等待T1执行完毕,跟着T1一起往前走,故在join()方法返回时,T1已经执行完成,因此i总是100000。

此时我们来看下join()方法的源码:

package java.lang;

public class Thread implements Runnable {
    
  	public final void join() throws InterruptedException {
        join(0);
    }
  
		public final synchronized void join(long millis) throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
}

注意这一行代码:

if (millis == 0) {
  while (isAlive()) {
    wait(0);
  }
}

join()方法的本质是让调用线程wait()方法在当前线程对象实例上。

即它让调用线程在当前线程对象上进行等待。当线程执行完成后,被等待的线程会在退出前调用notifyAll()方法通知所有的等待线程继续执行。因此,值得注意的一点是:不要在应用程序中,在Thread对象实例上使用类似wait()方法或者notify()方法等,因为这很有可能会影响系统API的工作,或者被系统API所影响。

所以建议:线程间的等待唤醒机制,最好不要用线程对象做同步锁!