【Java Concurrency】sleep()、wait()、notify()、notifyAll()的用法与区别

>关于本文

本文介绍sleep()、wait()、notify()、notifyAll()方法,主要要理解:

  1. sleep()和wait()的区别。
  2. wait()与notify()、notifyAll()之前互相协调的关系。
  3. notify()与notifyAll()的区别。

> Thread.sleep(long),睡眠指定时间

此方法是让线程睡眠指定时间不释放锁(睡觉,当然要上锁,这个还用说么)。

此方法我貌似很少用,又似乎很常用。因为,在正式代码中我很少用到,而在测试代码中,却又经常用来模拟某某业务需时几秒的阻塞。

public class Sleep {

    public static void main(String[] args) {
        
        System.out.println("Start...");
        
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        System.out.println("End...");

    }

}

一般,还有个更易读的写法,一样的效果

import java.util.concurrent.TimeUnit;


public class Sleep {

    public static void main(String[] args) {
        
        System.out.println("Start...");
        
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        System.out.println("End...");

    }

}

> Object.wait(),等待(条件)

线程转到等待状态释放锁。(既然等待,当然得释放锁了,我们在等待、迎接贵宾时,也是敞开着大门的,哈哈)

持有锁的情况下才能调用此方法,通常搭配外层的循环以判断是否继续等待。

wait方法可以执行时间,也可不,由notify方法唤醒。

 

晚餐、客人、服务员的例子

/**
 * 晚餐
 */
public class Dinner {
    
    private String mainDish; // 主菜

    public String getMainDish() {
        return mainDish;
    }

    public void setMainDish(String mainDish) {
        this.mainDish = mainDish;
    }
    
}
View Code
/**
 * 客人
 */
public class Customer extends Thread {
    
    private Dinner d;
    
    public Customer(Dinner d) {
        super();
        this.d = d;
    }

    @Override
    public void run() {
        synchronized (d) {
            while (d == null || d.getMainDish() == null) {
                try {
                    System.out.println(currentThread().getName() + ", customer start to wait.");
                    d.wait();
                    System.out.println(currentThread().getName() + ", customer end to wait.");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            
            d.setMainDish(null); // 相当于把菜吃掉
            System.out.println(currentThread().getName() + ", customer eat the food.");
        }
        super.run();
    }
    
}
View Code
/**
 * 服务员
 */
public class Waiter extends Thread {
    
    private Dinner d;
    
    public Waiter(Dinner d) {
        super();
        this.d = d;
    }

    @Override
    public void run() {
        synchronized (d) {
            d.notify();
            d.setMainDish("牛扒"); // 相当于上菜
            System.out.println(currentThread().getName() + ", waiter notify.");
        }
        super.run();
    }

}
View Code
import java.util.concurrent.TimeUnit;


public class HowToUse {

    public static void main(String[] args) {
        Dinner d = new Dinner();
        Customer c = new Customer(d);
        Waiter w = new Waiter(d);
        
        c.start();
        
        /* 等待一段时间,目的让Customer线程先启动 */
        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        w.start();
    }

}
View Code

日志:

Thread-0, customer start to wait.
Thread-1, waiter notify.
Thread-0, customer end to wait.
Thread-0, customer eat the food.

当然,也可执行时长停止等待了:

public class Wait {

    public static void main(String[] args) {
        
        System.out.println("Start...");
        
        String s = "";
        synchronized (s) {
            try {
                s.wait(5000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        
        System.out.println("End...");

    }

}
View Code

> Object.notify()和Object.notifyAll(),唤醒等待的线程

一个是通知一个线程(通知哪一个线程是不确定的哦),另一个是唤醒全部线程

将HowToUse方法修改下:

import java.util.concurrent.TimeUnit;


public class HowToUse {

    public static void main(String[] args) {
        Dinner d = new Dinner();
        Customer c1 = new Customer(d);
        Customer c2 = new Customer(d);
        Waiter w = new Waiter(d);
        
        c1.start();
        c2.start();
        
        /* 等待一段时间,目的让Customer线程先启动 */
        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        w.start();
    }

}
View Code

就可以看到:

Thread-0, customer start to wait.
Thread-1, customer start to wait.
Thread-2, waiter notify.
Thread-0, customer end to wait.
Thread-0, customer eat the food.

Thread-0、Thread-1分别代表两个客人,waiter唤醒了Thread-0,属于Thread-0的客人停止等待,去吃大餐了。

将notify修改成notifyAll:

/**
 * 服务员
 */
public class Waiter extends Thread {
    
    private Dinner d;
    
    public Waiter(Dinner d) {
        super();
        this.d = d;
    }

    @Override
    public void run() {
        synchronized (d) {
            d.notifyAll();
            d.setMainDish("牛扒"); // 相当于上菜
            System.out.println(currentThread().getName() + ", waiter notify.");
        }
        super.run();
    }

}
View Code

可以看到日志:

Thread-0, customer start to wait.
Thread-1, customer start to wait.
Thread-2, waiter notify.
Thread-1, customer end to wait.
Thread-1, customer eat the food.
Thread-0, customer end to wait.
Thread-0, customer start to wait.

Thread-2通知大家后,Thread-0、Thread-1都停止等待了,只不过Thread-1抢到了食物,吃完了,所以Thread-0又得重新等待。

> 什么时候用notify,什么时候用notifyAll?

如果各线程等待的条件不一样,那么要用notifyAll,因为用notify只通知到一个线程,而那线程的不满足跳出等待的条件,那么不就不好了吗。

比如,有一位富有的客人,要求晚餐的主菜中要有红酒才满足,与普通客人等待的条件不一样,如果服务员只准备了牛扒没有红酒,而有用notify只通知了富有的客人,那么普通的客人就没被通知到了。

/**
 * 富有的客人
 */
public class WealthyCustomer extends Thread {
    
    private Dinner d;
    
    public WealthyCustomer(Dinner d) {
        super();
        this.d = d;
    }

    @Override
    public void run() {
        synchronized (d) {
            while (d == null || d.getMainDish() == null || !d.getMainDish().contains("红酒")) { // 富有的客人,要求的主菜要有红酒
                try {
                    System.out.println(currentThread().getName() + ", wealthy customer start to wait.");
                    d.wait();
                    System.out.println(currentThread().getName() + ", wealthy customer end to wait.");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            
            d.setMainDish(null); // 相当于把菜吃掉
            System.out.println(currentThread().getName() + ", wealthy customer eat the food.");
        }
        super.run();
    }
    
}
View Code
import java.util.concurrent.TimeUnit;


public class HowToUse {

    public static void main(String[] args) {
        Dinner d = new Dinner();
        Customer c1 = new Customer(d);
        WealthyCustomer c2 = new WealthyCustomer(d);
        Waiter w = new Waiter(d);
        
        c2.start(); // 将WealthyCustomer的线程的先启动,比较容易看到Waiter通知他
        c1.start();
        
        /* 等待一段时间,目的让Customer线程先启动 */
        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        w.start();
    }

}
View Code

将Waiter设置成notify,会看到如下日志:

Thread-1, wealthy customer start to wait.
Thread-0, customer start to wait.
Thread-2, waiter notify.
Thread-1, wealthy customer end to wait.
Thread-1, wealthy customer start to wait.

将Waiter设置成notifyAll,日志如下:

Thread-1, wealthy customer start to wait.
Thread-0, customer start to wait.
Thread-2, waiter notify.
Thread-0, customer end to wait.
Thread-0, customer eat the food.
Thread-1, wealthy customer end to wait.
Thread-1, wealthy customer start to wait.

> 参考的优秀文章

Thread.sleep(long millis) API doc

wait(long timeout) API doc

notify() API doc

notifyAll() API doc

《Java编程思想》,机械工业出版社

原文地址:https://www.cnblogs.com/nick-huang/p/4787571.html