Java多线程编程笔记5:等待/通知机制

476 阅读7分钟

不使用等待/通知机制进行线程通信

当两个线程,一个线程通过轮训机制来判断另一个线程的变化,然后满足条件时,自己再进行操作,从而完成了通信。但是这种机制,如果轮询时间间隔小,浪费了CPU资源;轮询时间相隔很大,可能在满足条件时错过。

多个线程之间通过共同访问一个变量进行通信,不仅花费了读取的时间,还不能确定独到的变量是否是想要的。

等待/通知机制

等待通知模式是Java中经典的线程通信方式,两个线程通过对同一对象调用等待wait()和通知notify()方法来进行通讯。线程A调用对象object的wait()方法进入挂机状态,等待某个条件满足,当其他线程的运行使得这个条件满足时,其他线程会调用notify()或者notifyAll()方法,线程A收到通知后,退出等待队列,进入可运行状态,进而执行后序操作。

wait()方法使得当前执行代码的线程等待。该方法时Object类的方法,将当前线程放入“预执行队列”中,在wait()处停止执行,直到接到通知或被中断为止。在调用wait()方法前,线程必须获得该对象的对象级别锁,也就是只能在同步方法或同步块中调用该方法。在执行后,当前线程释放锁。

**notify()**方法也要在同步块或同步方法中调用,也即在调用前,线程也必须获得该对象的对象级别锁。该方法用来通知哪些可能等待该对象的对象锁的其它线程,从等待队列中挑选一个线程,对其发出notify。等到执行notify()方法的线程退出同步代码,当前线程释放锁,wait的线程获得对象锁。

总结:

  • wait() 、notify()、notifyAll() 调用的前提都是获得了对象的锁(也可称为对象监视器)。
  • 调用 wait() 方法后线程会释放锁,进入 WAITING 状态,该线程也会被移动到等待队列中。
  • 调用 notify() 方法会将等待队列中的线程移动到同步队列中,线程状态也会更新为 BLOCKED
  • 从 wait() 方法返回的前提是调用 notify() 方法的线程释放锁,wait() 方法的线程获得锁。

示例代码:

package ch03.waitNotify.t2;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

class MyList{
    private static List list=new ArrayList();
    public static void add(){
        list.add("anyString");
    }
    public static int size(){
        return list.size();
    }
}

class ThreadA extends Thread {
    private Object lock;

    public ThreadA(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            synchronized (lock) {
               if(MyList.size()!=5){
                   System.out.println("wait begin "+new Date(System.currentTimeMillis()));
                   lock.wait();
                   System.out.println("wait end "+new Date(System.currentTimeMillis()));
               }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class ThreadB extends Thread {
    private Object lock;

    public ThreadB(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            synchronized (lock) {
                for(int i=0;i<10;i++){
                    MyList.add();
                    if(MyList.size()==5){
                        lock.notify();
                        System.out.println("notified");
                    }
                    System.out.println("添加了"+(i+1)+"个元素");
                    Thread.sleep(1000);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Run {
    public static void main(String[] args) throws InterruptedException {
        Object lock=new Object();
        ThreadA a=new ThreadA(lock);
        a.start();
        Thread.sleep(50);
        ThreadB b=new ThreadB(lock);
        b.start();
    }
}

运行结果:

wait begin Sun Dec 09 20:11:49 CST 2018
添加了1个元素
添加了2个元素
添加了3个元素
添加了4个元素
notified
添加了5个元素
添加了6个元素
添加了7个元素
添加了8个元素
添加了9个元素
添加了10个元素
wait end Sun Dec 09 20:11:59 CST 2018

运行结果说明,成功通知了线程A,但是线程B是在同步代码执行完毕后,才释放了锁,之后线程A才得到锁,进一步执行的。

每个锁对象都有两个队列,一个是就绪队列,存储了将要获得锁的线程,一个线程被唤醒后,进入就绪队列,等待CPU的调度;另外一个是阻塞队列,存储了被阻塞的进程,一个线程被wait后,进入阻塞队列,等待下一次被唤醒。

线程的状态转换

  1. 新建(New):创建后尚未启动。
  2. 可运行(Runnable):线程对象创建后,在调用它的start()方法,系统为此线程分配CPU资源,使其处于可运行状态,这是一个准备运行的状态。
    • 调用sleep()方法后经过的时间超过了指定的休眠时间。
    • 线程调用的阻塞IO返回,阻塞方法执行完毕。
    • 线程获得了试图同步的监视器
    • 线程正在等待通知,且其他线程发出了通知
    • 处于挂起状态的线程调用了resume恢复方法。
  3. 运行(Running):在Runnable状态下的线程获得了CPU时间片,执行程序代码。
  4. 阻塞(Blocking):线程因为某种原因放弃了CPU使用权,让出了CPU时间片,暂时停止运行。包括如下五种情况:
    • 线程调用sleep()方法
    • 线程调用了阻塞IO方法,在该方法返回前,被阻塞
    • 线程试图获得同步锁,该锁被其他线程持有(同步阻塞)
    • 线程等待通知(等待阻塞)
    • 程序调用了suspend方法挂起(避免该方法,易死锁)
  5. 死亡(Dead):线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。

interrupt() 与 wait()

当线程呈wait()状态时,调用线程的interrupt()方法会出现InterruptedException异常。注意,在执行同步代码块的过程中,遇到异常导致线程终止,锁也会被释放。

wait(long)

带参数的wait方法时等待某一时间内是否有线程对锁进行唤醒,如果有别的线程唤醒,可以唤醒;否则,如果超过这个时间则自动唤醒。

生产者/消费者模式的实现

多个生产者和多个消费者,操作的为一个栈,当栈容量为0时,消费者等待,生产者生产;当栈容量为1时,消费者消费,生产者等待。代码如下:

//MyStack.java 存放生产者生产的栈
public class MyStack {
    private List list = new ArrayList();

    synchronized public void push() {
        try {
            while (list.size() == 1) {
                this.wait();
            }
            list.add("anyStr " + Math.random());
            this.notifyAll();
            System.out.println("经过线程:"+Thread.currentThread().getName()+" push = " + list.size());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public String pop() {
        String returnValue = "";
        try {
            while (list.size() == 0) {
                System.out.println("pop操作中的线程:" + Thread.currentThread().getName() + " 正在waiting");
                this.wait();
            }
            returnValue = "" + list.get(0);
            list.remove(0);
            this.notifyAll();
            System.out.println("经过线程:"+Thread.currentThread().getName()+" pop = " + list.size());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return returnValue;
    }
}


//Consumer.java
public class Consumer {
    private MyStack myStack;

    public Consumer(MyStack myStack) {
        this.myStack = myStack;
    }

    public void popService() {
        System.out.println("pop = " + myStack.pop());
    }
}
//Producer.java
public class Producer {
    private MyStack myStack;

    public Producer(MyStack myStack) {
        this.myStack = myStack;
    }

    public void pushService() {
        myStack.push();
    }
}
//CThread.java:消费者线程
public class CThread extends Thread {
    private Consumer consumer;

    public CThread(Consumer consumer) {
        this.consumer = consumer;
    }

    @Override
    public void run() {
        while (true) {
            consumer.popService();
        }
    }
}
//PThread.java:生产者线程
public class PThread extends Thread {
    private Producer producer;

    public PThread(Producer producer) {
        this.producer = producer;
    }

    @Override
    public void run() {
        while (true) {
            producer.pushService();
        }
    }
}
//Run.java 运行代码
public class Run {
    public static void main(String[] args) {
        MyStack myStack = new MyStack();
        Producer producer1 = new Producer(myStack);
        Producer producer2 = new Producer(myStack);
        Producer producer3 = new Producer(myStack);
        Producer producer4 = new Producer(myStack);
        Producer producer5 = new Producer(myStack);

        PThread pThread1 = new PThread(producer1);
        PThread pThread2 = new PThread(producer2);
        PThread pThread3 = new PThread(producer3);
        PThread pThread4 = new PThread(producer4);
        PThread pThread5 = new PThread(producer5);
        pThread1.setName("producer1");
        pThread2.setName("producer2");
        pThread3.setName("producer3");
        pThread4.setName("producer4");
        pThread5.setName("producer5");

        pThread1.start();
        pThread2.start();
        pThread3.start();
        pThread4.start();
        pThread5.start();

        Consumer consumer1 = new Consumer(myStack);
        Consumer consumer2 = new Consumer(myStack);
        Consumer consumer3 = new Consumer(myStack);
        Consumer consumer4 = new Consumer(myStack);
        Consumer consumer5 = new Consumer(myStack);

        CThread cThread1 = new CThread(consumer1);
        CThread cThread2 = new CThread(consumer2);
        CThread cThread3 = new CThread(consumer3);
        CThread cThread4 = new CThread(consumer4);
        CThread cThread5 = new CThread(consumer5);
        cThread1.setName("consumer1");
        cThread2.setName("consumer2");
        cThread3.setName("consumer3");
        cThread4.setName("consumer4");
        cThread5.setName("consumer5");

        cThread1.start();
        cThread2.start();
        cThread3.start();
        cThread4.start();
        cThread5.start();
    }
}

运行结果节选:

...
经过线程:producer3 push = 1
经过线程:consumer3 pop = 0
pop = anyStr 0.6413007971919816
pop操作中的线程:consumer3 正在waiting
pop操作中的线程:consumer1 正在waiting
pop操作中的线程:consumer2 正在waiting
pop操作中的线程:consumer5 正在waiting
经过线程:producer5 push = 1
经过线程:consumer4 pop = 0
pop = anyStr 0.2518672556962076
pop操作中的线程:consumer4 正在waiting
经过线程:producer2 push = 1
经过线程:consumer4 pop = 0
pop = anyStr 0.8942454272073399
经过线程:producer4 push = 1
经过线程:consumer5 pop = 0
pop = anyStr 0.4275474469724262
pop操作中的线程:consumer5 正在waiting
pop操作中的线程:consumer2 正在waiting
pop操作中的线程:consumer1 正在waiting
pop操作中的线程:consumer3 正在waiting
...

可以看出,栈的大小一致要么为1,要么为0,说明没有出现错误。同时要注意在Mystack中,push和pop都使用的是while语句进行判断,否则使用if语句会出错。另外,如果使用notify()函数可能会唤醒的是同类(消费者唤醒消费者),而导致线程一直waiting的假死状态,因此使用notifyAll()。

参考资料