一、图解方法
Thread和Object方法概览.png二、wait,notify,notifyAll方法详解
1. 基本用法
有时,我们想让一个线程或多个线程先去休息一下,等到我们后续需要,或者它的条件成熟的时候,再去唤醒它。这个就是wait, notify, notifyAll的作用了。一旦进入到了休息阶段,就进入了阻塞状态。线程执行wait方法,必须拥有这个对象的monitor锁。调用wait后,线程会进入阻塞,直到以下四种情况之一发生时,才会被唤醒:
- 另一个线程调用这个对象的notify()方法且刚好被唤醒的是本线程
- 另一个线程调用这个对象的notifyAll()方法
- 过了wait(long timeout)规定的超时时间,如果传入0就是永久等待
- 线程自身调用了interrupt()
notify只会唤醒一个线程,至于是哪个线程,取决于jvm的选择,java并没有做过多控制,notify和wait都需要在synchronized关键字里执行,在synchronized外执行,会抛出异常。一旦被唤醒,线程就不是等待的状态,会被重新调度。notifyAll会把所有等待的线程一次唤醒。至于哪一个线程会获得到锁,就看操作系统的调度了。
下面的代码展示wait和notify的基本用法:证明运行wait方法会释放锁。
public class Wait {
public static Object object = new Object();
static class Thread1 extends Thread {
@Override
public void run() {
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "开始执行了");
try {
object.wait(); //会释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
//被唤醒后会重新获得锁
System.out.println("线程" + Thread.currentThread().getName() + "获取到了锁");
}
}
}
static class Thread2 extends Thread {
@Override
public void run() {
synchronized (object){
object.notify();
System.out.println("线程"+ Thread.currentThread().getName()+"调用了notify");
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread1 thread1 = new Thread1();
Thread2 thread2 = new Thread2();
thread1.start();
Thread.sleep(200);
thread2.start();
}
}
运行结果:
Thread-0开始执行了
线程Thread-1调用了notify
线程Thread-0获取到了锁
下面的代码展示wait和notifyAll的基本用法:
public class WaitNotifyAll implements Runnable {
private static final Object resourceA = new Object();
@Override
public void run() {
synchronized (resourceA) {
System.out.println(Thread.currentThread().getName() + " got resourceA lock.");
try {
System.out.println(Thread.currentThread().getName() + " waits to start.");
resourceA.wait();
System.out.println(Thread.currentThread().getName()+"'s waiting to end.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new WaitNotifyAll();
Thread threadA = new Thread(runnable);
Thread threadB = new Thread(runnable);
Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA){
resourceA.notifyAll();
System.out.println("ThreadC notified");
}
}
});
threadA.start();
threadB.start();
Thread.sleep(200);
threadC.start();
}
}
运行结果如下:
Thread-0 got resourceA lock.
Thread-0 waits to start.
Thread-1 got resourceA lock.
Thread-1 waits to start.
ThreadC notified
Thread-1's waiting to end.
Thread-0's waiting to end.
wait方法只会释放当前的锁:
public class WaitNotifyReleaseOwnMonitor {
private static volatile Object resourceA = new Object();
private static volatile Object resourceB = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
System.out.println("ThreadA got resourceA lock.");
synchronized (resourceB) {
System.out.println("ThreadA got resourceB lock.");
try {
resourceA.wait();
System.out.println("ThreadA releases resourceA lock.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resourceA){
System.out.println("ThreadB got resourceA lock.");
synchronized (resourceB){
System.out.println("ThreadB got resourceB lock.");
}
}
}
});
thread1.start();
thread2.start();
}
}
运行结果:
ThreadA got resourceA lock.
ThreadA got resourceB lock.
ThreadB got resourceA lock.
2. 特点、性质
- 必须先拥有monitor才是使用
- 使用notify只能唤醒一个,但是唤醒哪一个需要操作系统决定
- 都属于Object
- 这些方法都是比较底层的实现,上层封装给我们的有类似的功能:Condition
- 注意释放锁的顺序,避免发生死锁。
三、手写生产者消费者设计模式
1. 为什么需要生产者消费者模式?
解决生产者和消费者相互等待,配合困难。把生产方和消费方解耦,降低配合难度。生产者看见队列满了,就不再生产,消费者看见队列空了,就不再先消费了。并且双方可以互相通知,生产者通知消费者,我已经生产完了。消费者通知生产者,我已经消费完了。
生产者模式1.png生产者和消费者通常会用到一个容器,通常是一个阻塞队列来解决他们的耦合问题。生产者和消费者不直接通讯,生产者将内容放到队列里,消费者从缓冲区去取。这样就把他们的能力进行了平衡。不至于一方太多或太少。
2. 具体代码演示
public class ProducerConsumerModel {
public static void main(String[] args) {
EventStorage eventStorage = new EventStorage();
Producer producer = new Producer(eventStorage);
Consumer consumer = new Consumer(eventStorage);
new Thread(producer).start();
new Thread(consumer).start();
}
}
class Consumer implements Runnable {
private EventStorage storage;
public Consumer(EventStorage storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
storage.take();
}
}
}
class Producer implements Runnable {
private EventStorage storage;
public Producer(EventStorage storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
storage.put();
}
}
}
class EventStorage {
private int maxSize;
private LinkedList<Date> storage;
public EventStorage() {
maxSize = 10;
storage = new LinkedList<>();
}
public synchronized void put() {
while (storage.size() == maxSize) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
storage.add(new Date());
System.out.println("仓库里有了" + storage.size() + "个产品。");
notify();
}
public synchronized void take() {
while (storage.size() == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("拿到了" + storage.poll() + "现在仓库还剩下:" + storage.size());
notify();
}
}
四、wait、notify的灵活使用
1. 两个线程交替打印0~100的奇偶性
public class WaitNotifyPrintOddEventWait {
private static final Object lock = new Object();
private static int count = 0;
public static void main(String[] args) {
new Thread(new TurningRunner()).start();
new Thread(new TurningRunner()).start();
}
//1.一旦拿到锁就去打印
//2.打印完,唤醒其他线程,自己就休眠
static class TurningRunner implements Runnable {
@Override
public void run() {
while (count <= 100) {
synchronized (lock) {
//拿到锁就打印
System.out.println(Thread.currentThread().getName() + ":" + count++);
lock.notify();
if(count <= 100){ //没有这句停不下来
try {
//如果任务还没结束,就让出当前的锁,并休眠
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
五、常见问题:
-
为什么wait()需要在同步代码块内使用,而sleep()不需要?
为了通信变得可靠,防止死锁或者永久等待的发生。比如要执行到wait(),线程切换到notify去执行,然后再切回到执行wait,逻辑就错了,容易发生永久等待,或死锁。所以把需要线程间互相配合的代码都放在同步代码块中了。而sleep是自己线程的,和其他线程没关系,所以不用放在synchronized中。 -
为什么线程通信的方法wait(),notify()和notifyAll()被定义在Object类里?而sleep定义在Thread类里?
锁是绑定到对象的,每个对象中都有几位是保存锁的状态。如果锁的概念放到Thread中,那么就无法实现,某一个线程持有多个锁的情况,没有目前灵活。
3.wait方法是属于Object对象的,那调用Thread.wait会怎么样?
线程退出的时候它会自动执行notify(),会使我们的流程受到干扰。Thread类不适合作为锁对象,其他的都适合,用个Object类就可以了。
4.如何选择用notify还是notifyAll()?
notifyAll之后所有的线程都会再次抢夺锁,如果某线程抢夺失败怎么办?
和回到初始状态一样,进入等待状态,等线程调度器调度。
5.用suspend()和resume()来阻塞线程可以吗?为什么?
推荐wait(),notify()来代替。
网友评论