• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

关于java:wait-和-notify-有坑

java 搞代码 3年前 (2022-01-27) 39次浏览 已收录 0个评论
文章目录[隐藏]

作者:忘净空

链接:https://www.gaodaima.com/p/91d…

兴许咱们只晓得wait和notify是实现线程通信的,同时要应用synchronized包住,其实在开发中晓得这个是远远不够的。接下来看看两个常见的问题。

问题一:告诉失落

创立2个线程,一个线程负责计算,一个线程负责获取计算结果。

<code class="java">public class Calculator extends Thread {
    int total;

    @Override
    public void run() {
        synchronized (this){
            for(int i = 0; i < 101; i++){
                total += i;
            }
            this.notify();
        }

    }
}

public class ReaderResult extends Thread {
    Calculator c;
    public ReaderResult(Calculator c) {
        this.c = c;
    }

    @Override
    public void run() {
        synchronized (c) {
            try {
                System.out.println(Thread.currentThread() + "期待计算结...");
                c.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + "计算结果为:" + c.total);
        }
    }


    public static void main(String[] args) {
        Calculator calculator = new Calculator();
        //先启动获取计算结果线程
        new ReaderResult(calculator).start();
        calculator.start();
        
    }
}

咱们会取得预期的后果:

Thread[Thread-1,5,main]期待计算结...
Thread[Thread-1,5,main]计算结果为:5050

然而咱们批改为先启动计算线程呢?

calculator.start();
new ReaderResult(calculator).start();

这是获取结算后果线程始终期待:

Thread[Thread-1,5,main]期待计算结...

问题剖析

打印出线程堆栈:

<code class="dart">"Thread-1" prio=5 tid=0x00007f983b87e000 nid=0x4d03 in Object.wait() [0x0000000118988000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000007d56fb4d0> (a com.concurrent.waitnotify.Calculator)
    at java.lang.Object.wait(Object.java:503)
    at com.concurrent.waitnotify.ReaderResult.run(ReaderResult.java:18)
    - locked <0x00000007d56fb4d0> (a com.concurrent.waitnotify.Calculator)

能够看出ReaderResult在Calculator上期待。产生这个景象就是常说的告诉失落,在获取告诉前,告诉提前达到,咱们先计算结果,计算完后再告诉,然而这个时候获取后果没有在期待告诉,等到获取后果的线程想获取后果时,这个告诉曾经告诉过了,所以就产生失落,那咱们该如何防止?能够设置变量示意是否被告诉过,批改代码如下:

<code class="java">public class Calculator extends Thread {
    int total;
    boolean isSignalled = false;

    @Override
    public void run() {
        synchronized (this) {
            isSignalled = true;//曾经告诉过
                for (int i = 0; i < 101; i++) {
                    total += i;
                }
                this.notify();
            }
    }
}

public class ReaderResult extends Thread {

    Calculator c;

    public ReaderResult(Calculator c) {
        this.c = c;
    }

    @Override
    public void run() {
        synchronized (c) {
            if (!c.isSignalled) {//判断是否被告诉过
                try {
                    System.out.println(Thread.currentThread() + "期待计算结...");
                    c.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "计算结果为:" + c.total);
            }

        }
    }

    public static void main(String[] args) {
        Calculator calculator = new Calculator();
        new ReaderResult(calculator).start();
        calculator.start();
    }
}

问题二:假唤醒

两个线程去删除数组的元素,当没有元素的时候期待,另一个线程增加一个元素,增加完后告诉删除数据的线程。

<code class="cpp">public class EarlyNotify{
    private List list;

    public EarlyNotify() {
        list = Collections.synchronizedList(new LinkedList());
    }

    public String removeItem() throws InterruptedException {

        synchronized ( list ) {
            if ( list.isEmpty() ) {  //问题在这
                list.wait();
            }

            //删除元素
            String item = (String) list.remove(0);
            return item;
        }
    }

    public void addItem(String item) {
        synchronized ( list ) {
            //增加元素
            list.add(item);
            //增加后,告诉所有线程
            list.notifyAll();
        }
    }

    private static void print(String msg) {
        String name = Thread.currentThread().getName();
        System.out.println(name + ": " + msg);
    }

    public static void main(String[] args) {
        final EarlyNotify en = new EarlyNotify();

        Runnable runA = new Runnable() {
            public void run() {
                try {
                    String item = en.removeItem();

                } catch ( InterruptedException ix ) {
                    print("interrupted!");
                } catch ( Exception x ) {
                    print("threw an Exception!!!\n" + x);
                }
            }
        };

        Runnable runB = new Runnable() {
            public void run() {
                en.addItem("Hello!");
            }
        };

        try {
            //启动第一个删除元素的线程
            Thread threadA1 = new Thread(runA, "threadA1");
            threadA1.start();

            Thread.sleep(500);

            //启动第二个删除元素的线程
            Thread threadA2 = new Thread(runA, "threadA2");
            threadA2.start();

            Thread.sleep(500);
            //启动减少元素的线程
            Thread threadB = new Thread(runB, "threadB");
            threadB.start();

            Thread.sleep(1000); // wait 10 seconds

            threadA1.interrupt();
            threadA2.interrupt();
        } catch ( InterruptedException x ) {}
    }
}

后果:

threadA1: threw an Exception!!!
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0

这里产生了假唤醒,当增加完一个元素而后唤醒两个线程去删除,这个只有一个元素,所以会抛出数组越界,这时咱们须要唤醒的时候在判断一次是否还有元素。

批改代码:

<code class="cpp">public String removeItem() throws InterruptedException {

      synchronized ( list ) {
          while ( list.isEmpty() ) {  //问题在这
              list.wait();
          }

          //删除元素
          String item = (String) list.remove(0);
          return item;
      }
}

期待/告诉的典型范式

从下面的问题咱们可演绎出期待/告诉的典型范式。

该范式分为两局部,别离针对期待方(消费者)和告诉方(生产者)。

期待方遵循原则如下:

  1. 获取对象的锁
  2. 如果条件不满足,那么调用对象的wait()办法,被告诉后仍要查看条件
  3. 条件满足则执行对应的逻辑

对应伪代码如下:

<code class="bash">synchronized(对象){
    while(条件不满足){
        对象.wait();
    }
    对应的解决逻辑
}

告诉方遵循原则如下:

  1. 取得对象的锁
  2. 扭转条件
  3. 告诉所以期待在对象上的线程

对应伪代码如下:

<code class="java">synchronized(对象){
    扭转条件
    对象.notifyAll();
}

近期热文举荐:

1.1,000+ 道 Java面试题及答案整顿(2021最新版)

2.终于靠开源我的项目弄到 IntelliJ IDEA 激活码了,真香!

3.阿里 Mock 工具正式开源,干掉市面上所有 Mock 工具!

4.Spring Cloud 2020.0.0 正式公布,全新颠覆性版本!

5.《Java开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞+转发哦!


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:关于java:wait-和-notify-有坑

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址