wait 和 notify以及notifyAll

(1)、方法介绍

## 1.wait、notify以及notifyAll都是Object对象的方法,他们必须在被 synchronized 同步的方法或代码块中调用,否则会报错。

## 2. 调用wait方法会使该线程进入等待状态,并且会释放被同步对象的锁。

## 3. notify操作可以唤醒一个因执行wait而处于阻塞状态的线程,使其进入就绪状态,被唤醒的线程会去尝试着获取对象锁,然后执行wait之后的代码。如果发出notify操作时,没有线程处于阻塞状态,那么该命令会忽略。注意执行notify并不会马上释放对象锁,会等到执行完该同步方法或同步代码块后才释放,下面会有例子来证明。

notify方法可以随机唤醒等待队列中等待同一共享资源的“一个”线程,使其退出等待队列进入可运行状态。

4. notifyAll方法可以唤醒等待队列中等待同一共享资源的“全部”线程从等待状态退出,进入可运行状态。此时,优先级最高的那个线程优先执行,但也有可能是随机执行,这取决于JVM虚拟机的实现。

测试在调用notify方法之后并不会马上释放对象锁,而是在执行完同步方法或同步方法块的时候才会释放。

代码如下:

public class MyList {

    private static List<String> list = new ArrayList<String>();
    
    public static void add(){
        list.add("sth");
    }
    
    public static int getSize(){
        return list.size();
    }
}

main方法以及两个线程类如下:

public class Test {

    public static void main(String[] args) throws InterruptedException {
     
        Object lock = new Object();
        ThreadA ta = new ThreadA(lock);
        Thread tta = new Thread(ta);
        
        tta.start();
        Thread.sleep(50);
        
        ThreadB tb = new ThreadB(lock);
        Thread ttb = new Thread(tb);
        ttb.start();
        
    }
}

class ThreadA implements Runnable{
    
    private Object mLock;
    
    public ThreadA(Object lock){
        mLock = lock;
    }

    public void run() {
        synchronized (mLock) {
            if(MyList.getSize() != 5){
                try {
                    System.out.println("before wait");
                    mLock.wait();
                    System.out.println("after wait");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        
    }
    
}

class ThreadB implements Runnable{
    
    private Object mLock;
    
    public ThreadB(Object lock){
        mLock = lock;
    }

    public void run() {
        synchronized (mLock) {
            for(int i = 0; i< 10; i++){
                MyList.add();
                if(MyList.getSize() == 5){
                    mLock.notify();
                    System.out.println("已发出notify通知");
                }
                System.out.println("增加"+(i+1)+"条数据");
            }
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("同步方法之外的方法");
    }
    
}

打印结果如下:

before wait
增加1条数据
增加2条数据
增加3条数据
增加4条数据
已发出notify通知
增加5条数据
增加6条数据
增加7条数据
增加8条数据
增加9条数据
增加10条数据
after wait
同步方法之外的方法

可以看出2点:

1.执行wait方法后会立马释放对象锁
2.执行notify不会立马释放对象锁,需等该同步方法或同步块执行完。注意是同步的内容执行完,而不是该线程的run方法执行完,从结果最后2句可以看出来。

最后说下 wait和sleep的区别,这也是面试经常面到的问题。

1.sleep是Thread类的方法而wait是Object类的方法。
2.sleep不会立马释放对象锁,而wait会释放。

写个小栗子来证明结论2:
在MyList类中增加一个方法:

public synchronized void doSth(){
        System.out.println("Thrad name : "+Thread.currentThread().getName()+" , begain doSth time : "+System.currentTimeMillis());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Thrad name : "+Thread.currentThread().getName()+" , end doSth time : "+System.currentTimeMillis());
    }

测试main方法

MyList myList = new MyList();
        ThreadC tc = new ThreadC(myList);
        tc.setName("C");
        ThreadD td = new ThreadD(myList);
        td.setName("D");
        tc.start();
        td.start();

ThreadC和ThreadD类如下:

class ThreadC extends Thread{
    private MyList myList;
    
    public ThreadC(MyList mlist){
        myList = mlist;
    }
    @Override
    public void run() {
        myList.doSth();
    }
}

class ThreadD extends Thread{
    private MyList myList;
    
    public ThreadD(MyList mlist){
        myList = mlist;
    }
    @Override
    public void run() {
        myList.doSth();
    }
}

结果如下:

Thrad name : C , begain doSth time : 1487647524673
Thrad name : C , end doSth time : 1487647527674
Thrad name : D , begain doSth time : 1487647527674
Thrad name : D , end doSth time : 1487647530674

可以看出线程C在调用sleep方法后并不会释放。

最后作点说明:
每个锁对象都有两个队列,就绪队列以及阻塞队列。就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,以等待CPU调度。反之一个线程被wait后就会进入阻塞队列,等待下一次唤醒。 也就是说一个线程被wait后会进入阻塞队列,待调用了 notify或notifyAll之后,该线程就会进入就绪队列。