[Java 多线程技术](七)使用wait/notify实现线程间通信

线程是操作系统中独立的个体,但这些个体如果不经过特殊处理就不能成为一个整体。线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更加强大,在大大提高CPU利用率的同时还会使程序员对各线程任务在处理的过程中进行有效的把控和监督。

1. 等待通知机制

1.1 不使用等待/通知机制实现线程间通信

  创建新项目,在实验中使用sleep()结合while(true)死循环法来实现多个线程间通信。代码如下:

public class MyList {
    private List list = new ArrayList<>();

    public void add() {
        list.add("王洪玉");
    }

    public int size() {
        return list.size();
    }

    public static void main(String[] args) {
        final MyList myList = new MyList();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                myList.add();
                System.out.println("当前线程," + Thread.currentThread().getName() + "添加了一个元素");
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            while (true) {

                if (myList.size() == 5) {
                    System.out.println("当前线程收到通知:" + Thread.currentThread().getName() + "list size = 5线程停止。。。");
                    throw new RuntimeException();
                }
            }
        }, "t2");


        t1.start();
        t2.start();
    }

}

运行结果:

分析:
  虽然两个线程间实现了通信,但有一个弊端就是,线程t2不断的通过while(true)语句轮询机制来检测某一个条件,这样会造成很严重的CPU浪费。所以就需要有一种机制来实现减少CPU的资源浪费,而且还可以实现在多个线程间通信,它就是“wait/notify”机制。

1.2 什么是等待/通知机制?

使用wait/notify方法实现线程间通信,要注意以下两点:
(1) wait和notify必须配合synchronized关键字使用
(2)wait方法释放锁,notify方法不释放锁

  方法wait()的作用就是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait()所在的代码行处停止执行,直到接到通知或被中断终止。在调用wait()之前,必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。在执行wait()方法后,当前线程释放锁。在wait返回前,线程与其他线程竞争重新获得锁。如果调用wait()时没有持有适当的锁,则抛出IllegalMonitorStateException,它是RuntimeException的一个子类,因此,不需要try-catch语句进行捕捉异常。
  方法notify()也要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁。如果调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并使它等待获取该对象的对象锁。需要说明的是,在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才能获取该对象锁。当第一个获得了该对象锁的wait线程执行运行完毕以后,他会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,还会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll。

1.3 使用等待/通知机制实现线程间通信

示例代码如下:

public class MyList1 {
    private List list = new ArrayList<>();

    public void add() {
        list.add("王洪玉");
    }

    public int size() {
        return list.size();
    }

    public static void main(String[] args){

        final MyList1 myList = new MyList1();

        //实例化出来一个lock
        //当使用wait/notify时,
        final Object lock = new Object();

        Thread t1 = new Thread(() -> {
            synchronized (lock){
                for (int i = 0; i < 10; i++) {
                    myList.add();
                    System.out.println("当前线程," + Thread.currentThread().getName() + "添加了一个元素");
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if(myList.size() == 5){
                        System.out.println("已经发出通知。。。。");
                        lock.notify(); //不释放锁,执行完for循环才释放锁
                    }
                }
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            synchronized (lock){
                if (myList.size() != 5) {
                    try {
                        System.out.println("进入t2。。。");
                        lock.wait(); //释放对象锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("当前线程收到通知:" + Thread.currentThread().getName() + "list size = 5线程停止。。。");
                    throw new RuntimeException();
                }
            }
        }, "t2");

        t2.start();
        t1.start();
    }

}

运行结果:
这里写图片描述

2. 等待通知机制的应用

2.1 使用wait/notify模拟Queue

BlockingQueue:顾名思义,首先它是一个队列,并且支持阻塞的机制,阻塞的放入和得到数据。我们要实现LinkedBlockingQueue下面两个简单的方法put和take。
put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断,知道BlockingQueue里面有空间再继续
take:取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入。
原理图如下所示:
这里写图片描述
实现代码如下:

public class MyQueue {

    //1.需要一个承装元素的集合
    private LinkedList<Object> list = new LinkedList<>();

    //2.需要一个计数器
    private AtomicInteger count = new AtomicInteger();

    //3.需要指定上限和下限
    private final int minSize = 0;
    private final int maxSize;

    //4.构造方法
    public MyQueue(int size){
        this.maxSize = size;
    }

    //5.初始化一个对象,用于加锁
    private final Object lock = new Object();

    //6.put()把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断,知道BlockingQueue里面有空间再继续
    public void put(Object obj){
        synchronized (lock){
            while (count.get() == this.maxSize){
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //6.1 加入元素
            list.add(obj);
            //6.2 计数器累加
            count.incrementAndGet();
            System.out.println("新加入的元素为:"+obj);
            //6.3 通知另外一个线程(唤醒)
            lock.notify();
        }
    }

    //7.take:取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入
    public Object take(){
        Object ret;
        synchronized (lock){
            while(count.get() == this.minSize){
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //7.1 移除元素
            ret = list.removeFirst();
            //7.2 计数器递减
            count.decrementAndGet();
            //7.3 唤醒另外一个线程
            lock.notify();
        }
        return ret;
    }

    public int getSize(){
        return this.count.get();
    }


    public static void main(String[] args) {
        MyQueue mq = new MyQueue(5);
        mq.put("a");
        mq.put("b");
        mq.put("c");
        mq.put("d");
        mq.put("e");
        System.out.println("当前容量的长度:"+mq.getSize());

        Thread t1 = new Thread(()->{
            mq.put("f");
            mq.put("g");
        },"t1");

        t1.start();

        Thread t2 = new Thread(()->{
            Object o1 = mq.take();
            System.out.println("移除的元素为"+o1);
            Object o2 = mq.take();
            System.out.println("移除的元素为"+o2);
        },"t2");

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

运行结果如下:
这里写图片描述

上面的例子也是经典的生产者/消费者模式的实现,我们下一篇博客再详细了解等待通知最经典的案例:“生产者/消费者”模式。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值