笔记 2 · wait()、notify()

Object 类中方法

final void wait() throws InterruptedException { wait(0) };

  • 重要特性:当前线程等待(进入阻塞状态),主动释放锁

final native void wait(long timeout) throws InterruptedException {…};

  • 调用wait() 时,首先需要确保调用了wait() 方法的线程已经持有了对象的锁(monitor)。
  • 调用wait() 方法的代码片段需要放在一个 synchronized块 或 synchronized方法中,这
    样才可以确保线程在调用wait()方法 前已经获取到了对象的锁。
  • 当调用wait() 后,当前线程陷入等待状态,该线程会主动释放掉 这个对象的锁,进入到等待
    集合( wait set )。直到其他线程调用相同对象的notify()或notifyAll() 方法将其唤醒。
  • 一旦这个线程被其他其他线程唤醒后,该线程就会与其他线程一同开始竞争这个对象的锁(公
    平竞争);
  • 只有当该线程获取到了这个对象的锁后,线程才会恢复到wait()方法调用前所处的状态,在继
    续往下执行。
  • 在单参数版本中,中断和虚假唤醒是可能的,并且此方法应始终在循环中使用:
    synchronized (obj) {
    while (< condition does not hold >)
    obj.wait();
    … // Perform action appropriate to condition
    }
  • 如果在没有拿到这个对象的锁时,调用wait()方法,会导致非法参数异常:
    java.lang.illegalargumentexception
  • 发生以下四种情况之一,才会唤醒线程:
  • 1、另一个线程调用这个对象的notify方法,唤醒线程,而当前线程恰好被选中(随机)。
  • 2、其他一些线程为此对象调用notifyAll方法。
  • 3、其他线程中断当前线程。
  • 4、指定的实时时间已过去,或多或少。但是,如果入参为0,则不考虑超时时长。
  • 【 wait(long timeout) 】 等待到指定的时间(毫秒)。

final native void notify() {…};

  • 当调用对象的notify()方法时,它会随机唤醒该对象等待集合( wait set ) 中的任意一个线程,如果有多个线程都在等待着这个对象的锁,会随机选择唤醒其中一个线程。
  • 被唤醒的线程是无法继续执行的,只有当前正在执行的线程 放弃或者让渡了 这个对象的锁,且被唤醒的线程在与其他线程公平竞争时抢到了对象的锁,被唤醒的线程才会继续往下执行。
  • 此方法只能由已经持有了对象锁(monitor)的线程调用。线程通过以下三种方式之一可以持有对象的锁(monitor):
  • 1、执行这个对象,被标识为 synchronized 的方法,获得对象的锁。
  • 2、执行这个对象,一个 synchronized 语句块,获得括号里()对象的锁。
  • 3、对于 class类型的对象,通过执行 这个类一个被标识为 synchronized 的静态方法,获得对象的锁。
  • 在某一个时刻,只有唯一一个线程可以拥有对象的锁。

final native void notifyAll() {…};

  • 当调用对象的notifyAll()方法时,它会唤醒对象等待集合( wait set ) 中的所有处于等待(阻塞)状态的线程,这些线程被唤醒后,又会开始竞争对象的锁。

在这里插入图片描述

总结:wait()、notify()、notifyAll()

  1. 当调用wait() 时,首先需要确保调用了wait() 方法的线程已经持有了对象的锁(monitor)。(如果在没有拿到这个对象的锁时,调用wait()方法,会导致非法参数异常:
    java.lang.illegalargumentexception)
  2. 当调用wait() 后,该线程会主动释放掉这个对象的锁,进入到等待集合( wait set )。
  3. 当线程调用了wait() 后,进入到等待状态时,可以等待其他线程调用相同对象的notify()或notifyAll() 方法使得自己被唤醒。
  4. 一旦这个线程被其他其他线程唤醒后,该线程就会与其他线程一同开始竞争这个对象的锁(公平竞争);只有当该线程获取到了这个对象的锁后,线程才会恢复到wait()方法调用前所处的状态,在继续往下执行。
  5. 调用wait() 方法的代码片段需要放在一个 synchronized块 或 synchronized方法中,这样才可以确保线程在调用wait()方法 前已经获取到了对象的锁。
  6. 对于一个单参数的版本来说,中断和一些虚假的唤醒,是有可能发生的。所以wait()方法应该始终处于循环当中。
  7. 当调用对象的notify()方法时,它会随机唤醒该对象等待集合( wait set ) 中的任意一个线程,当某个线程被唤醒后,它就会与其他线程一同竞争对象的锁。
  8. 当调用对象的notifyAll()方法时,它会唤醒对象等待集合( wait set ) 中的所有线程,这些线程被唤醒后,又会开始竞争对象的锁。
  9. 在某一个时刻,只有唯一一个线程可以拥有对象的锁。

案例一 输出1010…

描述:
 编写一个多线程程序,实现:
 1.存在一个对象,该对象有一个int类型的成员变量counter,该成员变量初始值为0.
 2.创建2个线程,其中一个线程对该对象的成员变量counter增1,另一个线程对该对象的成员变量减1.
 3.输出该对象成员变量counter每次变化的值
 4.最终输出的结果为:1010101010101010101010101010101010101010101...
找错误:
// 对象
class MyObject {
    // Integer 默认值为 null
    public volatile Integer num = 0;

    public void increase() {  num++; }
    public void decrease() {  num--; }
}

// 执行添加操作的线程
class MyIncreaseThread implements Runnable {
    private MyObject myObject;

    public MyIncreaseThread(MyObject myObject) {  this.myObject = myObject;  }

    @Override
    public void run() {
        synchronized (myObject) {
            for (int i = 0; i < 30; i++) {
                while (myObject.num == 1) {
                    myObject.notify();
                    try {
                        myObject.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                myObject.increase();
                System.out.print(myObject.num);
            }
        }
    }
}

// 执行减去操作的线程
class MyDecreaseThread implements Runnable {
    private MyObject myObject;

    public MyDecreaseThread(MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run() {
        synchronized (myObject) {
            for (int i = 0; i < 30; i++) {
                while (myObject.num == 0) {
                    myObject.notify();
                    try {
                        myObject.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                myObject.decrease();
                System.out.print(myObject.num);
            }
        }
    }
}

class Test2 {
    public static void main(String[] args) {
        MyObject myObject = new MyObject();
        MyIncreaseThread increaseThread = new MyIncreaseThread(myObject);
        MyDecreaseThread decreaseThread = new MyDecreaseThread(myObject);
        Thread thread = new Thread(increaseThread, "添加线程");
        thread.start();
        Thread thread1 = new Thread(decreaseThread, "减去线程");
        thread1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // TERMINATED
        System.out.println(thread.getState());
        // WAITING
        System.out.println(thread1.getState());
    }
    // 输出结果:【添加线程】正常关闭,【减去线程】陷入等待(无期限的死等)
    // 10101010101010101010101010101010101010101010101010101010101
    // TERMINATED
	// WAITING
}
分析:为什么【减去线程】不能正常结束呢?
1、因为【增加线程】在最后的一次循环中,判断  if (myObject.num == 1)  结果为false。
2、然后将执行 num++,最后输出 num的值1。【添加线程】完成了所有任务,线程结束。
3、所以我们看到输出的最后一个数字是1,【添加线程】的状态是TERMINATED。
4、而【减去线程】还剩下最后一次循环,因为没人将其唤醒,所以会一直处于等待。
解决方法:
@Override
public void run() {
    synchronized (myObject) {
        for (int i = 0; i < 30; i++) {
            if (myObject.num == 0) {
                try {
                    myObject.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            myObject.decrease();
            System.out.print(myObject.num);
             // 所有的代码都不变,只要将MyDecreaseThread、MyIncreaseThread类中 
             // notify() 的方法调用,放在最后一行就行。
             // 这样可以保证,线程执行完成后,一定会唤醒另一个线程。
            myObject.notify();  
        }
    }
}
// 输出结果:【添加线程】正常关闭,【减去线程】陷入等待(无期限的死等)
// 101010101010101010101010101010101010101010101010101010101010
// TERMINATED
// TERMINATED

案例二 生产者 / 消费者

描述:
 写一个固定容量 同步容器,拥有put()、get()方法,getCount()方法,能够支持2个生产者线程以及
 10个消费者线程的阻塞调用。

 思路:
 当生产者达到最大数量的时候,让生产者线程wait阻塞等待,否则继续生产,同时通知消费者进行消费。
 当消费者消费到0个的时候,让消费者线程wait阻塞等待,否则继续消费,同时通知生产者进行生产。
实现方式一:wait() notifyAll()
class MyContainer<T> {
    volatile LinkedList<T> list = new LinkedList<T>();
    final private int MAX = 10; // 最多10个元素

    public synchronized void put(T num) {
        while (MAX == list.size()) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("添加线程:" + Thread.currentThread().getName());
        list.add(num);
        this.notifyAll();
    }

    public synchronized T get() {
        while (0 == list.size()) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.notifyAll();
        T t = list.removeFirst();
        System.out.println(Thread.currentThread().getName() + " get = " + t);
        return t;
    }
}

class Test5 {
    public static void main(String[] args) {
        MyContainer myContainer = new MyContainer();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int i1 = 0; i1 < 1; i1++) {
                    myContainer.get();
                }
            },"消费者线程-" + i).start();
        }

        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                for (int i1 = 0; i1 < 5; i1++) {
                    myContainer.put(i1);
                }
            },"生产者线程-" + i).start();
        }
        // 这种实现方式有个问题:就是生产者线程调用notifyAll通知的时候,
        // 有可能通知到生产者线程,消费者线程调用notifyAll通知的时候有可能通知到消费者线程,
        // 所以还可以改进,下面使用Lock和Condition,解决这个问题。
    }
    //输出信息
    //添加线程:生产者线程-0
    //添加线程:生产者线程-0
    //添加线程:生产者线程-0
    //添加线程:生产者线程-0
    //添加线程:生产者线程-0
    //消费者线程-8 get = 0
    //消费者线程-9 get = 1
    //消费者线程-7 get = 2
    //消费者线程-6 get = 3
    //消费者线程-5 get = 4
    //添加线程:生产者线程-1
    //添加线程:生产者线程-1
    //添加线程:生产者线程-1
    //添加线程:生产者线程-1
    //添加线程:生产者线程-1
    //消费者线程-0 get = 0
    //消费者线程-1 get = 1
    //消费者线程-2 get = 2
    //消费者线程-3 get = 3
    //消费者线程-4 get = 4
}
实现方式二:Lock Condition

实现方式二

案例三 线程监控

描述:
 实现一个容器,提供两个方法,add、size
 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
class MyContainer {
    public volatile List<Integer> list = new ArrayList<>(10);
    public boolean flag;

    public void add(int num) {
        System.out.println(num);
        list.add(num);
    }
    public int getSize() {
        return list.size();
    }

    public static void main(String[] args) {
        MyContainer container = new MyContainer();
        new Thread(() -> {
            synchronized (container) {
                while (container.getSize() != 5) {
                    try {
                        container.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("集合数量到5了");
                container.flag = true;
                container.notify();
            }
        },"监控线程").start();

        new Thread(() -> {
            synchronized (container) {
                for (int i = 0; i < 10; i++) {
                    if (5 == i) {
                        try {
                            container.notify();
                            container.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    if (container.flag) {
                        System.out.println("线程执行结束");
                        break;
                    }
                    container.add(i);
                }
            }
        }, "添加线程").start();
    }
}
	// 输出信息
	// 0
	// 1
	// 2
	// 3
	// 4
	// 集合数量到5了
	// 线程执行结束

在这里插入图片描述

注:资料主要来源【张龙

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值