多线程之等待通知机制(九)

一 什么是等待通知机制

在单线程编程中,要执行的操作需要满足一定的条件才能执行,可以把这个操作放在if语句中。

在多线程中,可能A线程的条件没有满足只是暂时的,稍后其他的线程B可能会更新条件使得A线程的条件得到满足,可以将A线程暂停,直到他的条件得到满足后,我们在将A线程唤醒。他的伪代码:

	atomics{
//原子操作

	while(条件不成立){
		//等待
}
//条件满足后,当前线程被唤醒,就继续执行下面的操作
}

二、 等待通知机制实现

Object类中的wait()方法,可以使当前代码的线程等待,暂停执行,当先线程转入blocked堵塞状态,直到接到通知或被中断为止。线程等待,释放锁对象,

注意:

  1. wait()方法只能在同步代码块中,由锁对象调用
  2. 调用wait()方法后,当前线程会释放锁

其伪代码:

  //在调用wait()方法前要获取对象的内部锁

synchronized(锁对象){

	while(条件不成立){

		//通过锁对象来调用wait()方法暂停线程,释放锁对象

		锁对象.wait()}

 //线程的条件满足了,继续向下执行

}

Object类的notify()可以唤醒线程,该方法也必须在同步代码块中,由锁对象调用,没有使用锁对象调用wait()/notify()会抛出异常: IlegalMonitortateExption如果有多个等待的线程,notify()方法只能唤醒其中的一个。 在同步代码块中调用notify()方法后,不会立即释放锁对象,需要等当前同步代码块执行完后才会释放锁对象,一般将notify()方法放在同步代码块的最后。

它的伪代码:

  synchronized(锁对象){

	//修改保护条件的代码

	//唤醒其他线程

  锁对象.notify();

}

三、wait方法的基本使用

package com.dome.wait;

/**
 * @author qb
 * @version 1.0
 * @Description
 * 演示wait方法和notify方法,需要放在同步代码块中否则会产生java.lang.IllegalMonitorStateException异常
 *
 * 任何对象都可以调用wait/notify,这连个方法是从Object类继承来的
 * @date 2021/3/9 21:20
 */
public class Test01 {

    public static void main(String[] args) {
        try {


            String test = "wkcto";//java.lang.IllegalMonitorStateException

            test.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

四、 notify方法的简单实例

notify()方法后不会立即释放锁对象

package com.dome.wait;

/**
 * @author qb
 * @version 1.0
 * @Description
 * 需要通过notify来唤醒等待的线程
 * @date 2021/3/9 21:28
 */
public class Test03 {

    public static void main(String[] args) throws InterruptedException {
        //定义一个字符串作为锁对象
        String lock ="wkcto";

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
              synchronized (lock){
                  System.out.println("线程1开始等待: "+System.currentTimeMillis());
                  try {
                      lock.wait();//线程等待,释放锁对象,当先线程转入blocked堵塞状态
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  System.out.println("线程1结束等待:"+System.currentTimeMillis());
              }
            }
        });
        //定义第二个线程,在第二个线程中唤醒第一个线程
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                //notify()方法也需要在同步代码块中调用
                synchronized (lock){
                    System.out.println("线程2开始唤醒: "+System.currentTimeMillis());
                    lock.notify(); //唤醒在lock等待的线程,唤醒某一个线程
                    System.out.println("线程2结束唤醒: "+System.currentTimeMillis());

                }
            }
        });
        t1.start();  //开启t1线程,t1线程等待
        Thread.sleep(3000);  //main线程睡眠3秒,确保t1线程入睡

        t2.start(); //t1线程3秒后再开启t2线程唤醒t1线程


    }
}

演示notify()方法后不会立即释放锁对象

package com.dome.wait;

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

/**
 * @author qb
 * @version 1.0
 * @Description
 * notify()不会立即释放锁对象
 * @date 2021/3/9 21:39
 */
public class Test04 {

    public static void main(String[] args) {
        //定义一个list集合存储String数据
        List<String> list = new ArrayList<>();

        //定义第一个线程,当list集合中元素的数量不等于5时,线程等待
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (list){
                    if(list.size() != 5){
                        System.out.println("线程等待:"+System.currentTimeMillis());
                        try {
                            list.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("线程1被唤醒:"+System.currentTimeMillis());

                    }
                }
            }
        });

        //定义第二个线程,向list集合中添加元素
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (list){
                    for (int i = 0; i < 10; i++) {
                        list.add("data--"+i);
                        System.out.println("线程2添加第"+(i+1)+"个数据");
                        //判断元素的数量是否满足唤醒线程1的条件
                        if(list.size() == 5){
                            list.notify(); //唤醒线程
                            System.out.println("线程2已经发出唤醒通知");
                        }
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });

        t1.start();
        //为了确保t2在t1之后开启,
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }

}

执行结果
在这里插入图片描述

五、interrupt中断线程的wait等待

当线程处于wait等待状态时,调用线程对象的interrupt方法会中断线程的等待状态,会产生InterruptedException异常

package com.dome.wait;

/**
 * @author qb
 * @version 1.0
 * @Description
 * Interrupt()会中断线程的wait等待
 * @date 2021/3/10 19:37
 */
public class Test05 {

    public static void main(String[] args) {

        SubThread t =new SubThread();
        t.start();

        try {
            //主线程睡眠两秒,确保子线程处于wait等待状态
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        t.interrupt();
    }

    //定义常量作为锁对象
    private static final Object lock = new Object();

    static class SubThread extends Thread{
        @Override
        public void run() {
            synchronized (lock){

                try {
                    System.out.println("开始等待");
                    lock.wait();
                    System.out.println("结束等待");

                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("等待被中断");
                }

            }
        }
    }
}

运行结果
在这里插入图片描述

六、notify和notifyAll方法区别

notify一次只能唤醒一个线程,如果有多个等待线程,只能随机唤醒其中的某一个,如果想要唤醒所有等待线程,需要调用notifyAll方法

package com.dome.wait;

/**
 * @author qb
 * @version 1.0
 * @Description
 * notify和notifyAll
 * @date 2021/3/10 19:45
 */
public class Test06 {

    public static void main(String[] args) {

        Object lock = new Object(); //定义一个对象作为子线程的锁对象

        SubThread t1 = new SubThread(lock);
        SubThread t2 = new SubThread(lock);
        SubThread t3 = new SubThread(lock);

        t1.setName("t1");
        t2.setName("t2");
        t3.setName("t3");

        t1.start();
        t2.start();
        t3.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //2秒后唤醒子线程,调用notify唤醒子线程
        synchronized (lock){
            //lock.notify(); //调用一次只能唤醒其中的一个线程,其他等待线程依然处于等待状态,对于处于等待状态的线程来说,错过了等待新号
            //也成为新号丢失
            lock.notifyAll();//唤醒所有等待线程
        }


    }

    static class SubThread extends Thread{

        private Object lock ; //定义实例对象,作为锁对象

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

        @Override
        public void run() {
           synchronized (lock){
               try {
                   System.out.println(Thread.currentThread().getName()+"--开始等待");
                   lock.wait();
                   System.out.println(Thread.currentThread().getName()+"---结束等待");
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        }
    }

}


运行结果
在这里插入图片描述

七、wait(login) 方法的使用

wait(long)带有long类型参数的wait等待,如果在参数指定的时间内,你没有被唤醒,超时后,会自动唤醒

package com.dome.wait;

/**
 * @author qb
 * @version 1.0
 * @Description
 * wait(long)
 * @date 2021/3/10 19:56
 */
public class Test07 {

    public static void main(String[] args) {

        final Object obj = new Object();

        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj){
                    try {
                        System.out.println("开始等待");
                        obj.wait(5000);
                        System.out.println("线程结束等待");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t.start();

    }


}

运行结果
在这里插入图片描述

八、 通知过早

线程wait等待后,可以调用notify唤醒线程,如果notify唤醒的过早,在等待之前就调用了notify,可能会打乱程序正常的执行逻辑
调用start方法的顺序,不一定是线程开启的顺序。

package com.dome.wait;

/**
 * @author qb
 * @version 1.0
 * @Description
 * notify通知过早
 * @date 2021/3/10 20:00
 */
public class Test08 {

    public static void main(String[] args) {

        final Object lock = new Object(); //锁对象

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock){
                    try {
                        System.out.println("t1开始等待");
                        lock.wait();
                        System.out.println("t1等待结束");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock){

                        System.out.println("t2 notify");
                        lock.notify();
                        System.out.println("t2 结束notify");

                }
            }
        });


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

        //如果t2线程先唤醒,就不让t1线程等待

    }


}

执行结果
在这里插入图片描述
解决通知过早

package com.dome.wait;

/**
 * @author qb
 * @version 1.0
 * @Description
 * notify通知过早
 * @date 2021/3/10 20:00
 */
public class Test09 {

    static boolean isFirst = true; //定义静态变量,作为是否第一个运行的标志

    public static void main(String[] args) {

        final Object lock = new Object(); //锁对象


        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    while (isFirst) { //是第一个开启的,就等待
                        try {
                            System.out.println("t1开始等待");
                            lock.wait();
                            System.out.println("t1等待结束");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock){

                        System.out.println("t2 notify");
                        lock.notify();
                        System.out.println("t2 结束notify");
                        isFirst =false;  //通知后将其改为false

                }
            }
        });


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

        //如果t2线程先唤醒,就不让t1线程等待

    }


}

运行结果
在这里插入图片描述

九、wait条件放声变化

在使用wait/notify模式时,wait条件发生变化,也可能做成逻辑混乱

package com.dome.wait;

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

/**
 * @author qb
 * @version 1.0
 * @Description
 * wait条件发生变换
 * 定义一个集合,通过一个线程向集合添加数据,另一个线程取数据,
 * 如果集合没数据,就等待
 * @date 2021/3/10 20:14
 */
public class Test10 {

    public static void main(String[] args) {

        //1.先定义添加数据的线程对象
        ThreadAdd threadAdd = new ThreadAdd();
        //2.定义取数据的线程对象
        ThreadGet threadGet = new ThreadGet();
        threadGet.setName("threadGet  1");
        //测试1:先开启添加数据线程,在开启取数据线程,大多数情况下会正常执行
//        threadAdd.start();
//        threadGet.start();
        //测试2:先开启取数据,在开启添加数据
//        threadGet.start();
//        threadAdd.start();
        //测试3:开启两个取数据线程,在开启添加线程
        /**
         * threadGet  1开始等待
         * threadGet  2从集合中去了data后,集合中数据数量0
         * threadGet  1结束等待
         * Exception in thread "threadGet  1"
         * threadGet取数据时,没有数据就等待
         * threadAdd获得cpu执行权添加数据,把threadGet线程给唤醒
         * 但是threadGet2获得了执行权,正常取数据,threadGet线程
         * 获得cpu执行权之后,打印结束等待,再执行reomve方法,现在list集合已经没有数据
         *
         * 出现异常原因,向list添加一个数据,remove了两次
         *
         * 如何解决:
         *  当等待的线程唤醒后,先判断list中有没有数据,吧threadGet中的if改为while
         *
         */
        ThreadGet threadGet2 = new ThreadGet();
        threadGet2.setName("threadGet  2");
        threadGet.start();
        threadGet2.start();
        threadAdd.start();
    }

    //定义list集合
    static List list = new ArrayList<>();

    //定义方法从集合中取数据
    public static void subtract(){
        synchronized (list) {
            while (list.size() == 0) {
                try {
                    System.out.println(Thread.currentThread().getName()+"开始等待");
                    list.wait();
                    System.out.println(Thread.currentThread().getName()+"结束等待");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Object date = list.remove(0);
            System.out.println(Thread.currentThread().getName()+"从集合中去了"+date+"后,集合中数据数量"
            +list.size()
            );
        }
    }
    //定义方法向集合中添加数据后,通知等待的线程取数据
    public static void add(){
        synchronized (list){
            list.add("data");
            System.out.println(Thread.currentThread().getName()+"存储了1数据");
            list.notifyAll();
        }
    }

    //定义线程加数据
    static class ThreadAdd extends Thread{
        @Override
        public void run() {
            add();
        }
    }

    //定义线程取数据
    static class ThreadGet extends Thread{
        @Override
        public void run() {
            subtract();
        }
    }
}

运行结果
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值