专栏首页一只小菜鸟Java Wait错误用法
原创

Java Wait错误用法

Java Wait 错误用法

实例观察

  • 先来看看一段代码:
new Thread(() -> {
           synchronized (ReleaseLockDemo.class) {
               System.out.printf("线程[%s]进入1号\n", Thread.currentThread().getName());
               try {
//                    Thread.sleep(1000);
                    Thread.currentThread().wait();

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.printf("线程【%s】退出1号\n", Thread.currentThread().getName());
            }
        }).start();

        new Thread(() -> {
            synchronized (ReleaseLockDemo.class) {
                System.out.printf("线程[%s] 进入2号\n", Thread.currentThread().getName());

                System.out.printf("线程【%s】退出2号\n", Thread.currentThread().getName());
            }
        }).start();

运行结果

  • 这段代码的结果是:
线程[Thread-0]进入1号
Exception in thread "Thread-0" 线程[Thread-1] 进入2号
线程【Thread-1】退出2号
java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at com.concurrency.ReleaseLockDemo.lambda$main$0(ReleaseLockDemo.java:17)
	at java.lang.Thread.run(Thread.java:748)

实例疑问

为什么会是这样的结果呢?按道理来说, 应该是下面这个结果才对呀。它为什么会在1号线程等待的时候,2号线程运行了呢,它不应该是要等待1号线线程中的锁释放了才能运行的吗?又为什么会报两个错呢?

线程[Thread-0]进入1号
线程【Thread-0】退出1号
线程[Thread-1] 进入2号
线程【Thread-1】退出2号

那我们先总结下问题:

  1. 为什么会在1号线程等待的时候,2号线程运行了
  2. 为什么会报Exception in thread "Thread-0"
  3. 为什么会报java.lang.IllegalMonitorStateException

那么带着问题我们来分析下这段代码。

实例分析

  • 为什么会在1号线程等待的时候,2号线程运行了?
    • 就目前的代码而言,我们的锁对象都是ReleaseLockDemo.class,在两个同步代码块中,用同一个锁,一个代码块运行了,而另一个要运行的话,只有前面的锁释放了后面的代码块才能正常运行。可是在前面的1号线程只运行到了一半就直接运行2号线程了,中间只做了一个wait操作,难道wait操作会引发锁的释放吗?
    • 这是因为在调用wait()方法时,monitor被释放了,并且进入休眠状态,然后唤醒其他的线程(可能一个或多个),此时其他线程是可以重新获取到该monitor object的。
  • 那么我们来看看这个报错IllegalMonitorStateException, 这是一个monitor报错
    • 要想了解这个报错,首先得了解下什么是monitor
      • monitor就是监视器,操作系统在面对 进程/线程间同步的时候,所支持的一些同步原语,一般的monitor实现模式是编程语言在语法上提供语法糖,而如何实现monitor机制,则属于编译器的工作,Java就是这么干的。
      • monitor的重要特点是,同一时刻,只有一个进程/线程能进入monitor中定义的临界区,这使得monitor能够达到互斥的效果。但仅仅有呼哧的作用是不够的,无法进入monitor临界区的进程/线程,他们应该被阻塞,并且在必要的时候会被唤醒。
      • 在Java中的具体实现就是Synchronized;Synchronized关键字在使用时,往往需要指定一个对象与之关联(如:synchronized(this)),而这个对象就是monitor object。
      • 一个线程通过调用某个对象的wait()方法释放该对象的monitor并进入休眠状态,知道其他线程获取来被该线程释放的monitor并调用该对象的notify()或者notifyAll()后再次竞争获取该对象的monitor。这也解释了前面的问题,为什么2号线程会运行。
      • 只有拥有该对象monitor的线程才可以调用该对象的notify()和notifyAll()方法;如果没有该对象monitor的线程调用了该对象的notify()或者notifyAll()方法将会抛出java.lang.IllegalMonitorStateException
    • 调用wait()方法必须要用拥有该对象monitor的线程才可以正常调用,而我们的代码中synchronzed所锁住的对象是ReleaseLockDemo.class。因此,执行该程序后报java.lang.IllegalMonitorStateException错误。
  • 至于为什么会报Exception in thread "Thread-0" 错,这个错跟后面的IllegalMonitorStateException是同一个错,只是因为当时线程休眠了,没有执行完而已。

如果换成拥有当前类的对象呢

  • 既然要调用wait()方法那么如果我们把monitor object换成当前类的对象会不会就不会报错了呢?

上代码

public static void main(String[] args) {
        WaitMethodTest wmt = new WaitMethodTest();
        wmt.run1();
        wmt.run2();
    }

    private  void run2() {
        new Thread(() -> {
            synchronized (this) {
                System.out.printf("线程[%s] 进入2号\n", Thread.currentThread().getName());

                System.out.printf("线程【%s】退出2号\n", Thread.currentThread().getName());
            }
        }).start();
    }

    private  void run1() {
        new Thread(() -> {
            synchronized (this) {
                System.out.printf("线程[%s]进入1号\n", Thread.currentThread().getName());
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.printf("线程【%s】退出1号\n", Thread.currentThread().getName());
            }
        }).start();
    }

运行结果:

  线程Thread-0进入1号
  线程Thread-1 进入2号
  线程【Thread-1】退出2号

结果分析

  • 结果确实没有报错了,但是它好像也没有停止运行
  • 这是因为当2号线程运行完成后并没有唤醒1号线程,1号线程还在睡眠状态,所以整个程序没有停止运行。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 网站建设的完整流程和步骤,新手必看

    现在互联网已经非常普及,网站建设的需求也是越来越多,那么网站建设怎么做呢?下面小熊优化的小编就给大家说一下网站建设的流程:

    用户4831957
  • 【技巧】用于检测未知恶意软件的深度学习方法

    目前,所有主要的反病毒供应商都在朝着机器学习方法靠拢,以求跟上不断变化的危险环境。这是个好消息。然而,随着每天有超过100万个新的恶意软件被释放,传统的机器学习...

    AiTechYun
  • 关于Android中工作者线程的思考

    本文为 InfoQ 中文站特供稿件,首发地址为:http://www.infoq.com/cn/articles/android-worker-thread 如...

    技术小黑屋
  • 你真的了解AsyncTask?

    虽说现在做网络请求有了Volley全家桶和OkHttp这样好用的库,但是在处理其他后台任务以及与UI交互上,还是需要用到AsyncTask。但是你真的了解Asy...

    weishu
  • windows自定义程序开机启动

    因为操作较为复杂,所以有人写了一款软件,叫SrvanyUI,集成了了srvany.exe,新建服务较为简单(打开软件,点增加服务,选自建服务即可达到相同效果)。...

    明哥的运维笔记
  • 并发编程导论

    随着硬件性能的迅猛发展与大数据时代的来临,并发编程日益成为编程中不可忽略的重要组成部分。简单定义来看,如果执行单元的逻辑控制流在时间上重叠,那它们就是并发(Co...

    王下邀月熊
  • 学习大数据要有这样的学习思路才行?

    我们在系统学习大数据的之前,要先了解大数据开发是在什么系统平台下进行的。所以我们在学之前要先学习Linux的知识,这部分显得格外的重要。

    用户2292346
  • 【深度学习】自动驾驶:使用深度学习预测汽车的转向角度

    近年来,特别是在10年前Darpa挑战赛成功之后,全自动驾驶汽车的开发速度大大加快。自动驾驶汽车由许多部件组成,其中最关键的部件是驱动它的传感器和人工智能软件。...

    AiTechYun
  • 企业做网站要有合理的预期和预算

    在经历了多家企业网站制作的过程后,美耐思发现如果企业主一心只想着怎么降低网站制作费用做成自己满意的网站时,往往事与愿违,钱是花少了,可是网站却做的较糟糕。无论是...

    天津做网站-美耐思
  • 比Redis快5倍的中间件,为啥这么快?

    KeyDB项目是从Redis fork出来的分支。众所周知Redis是一个单线程的kv内存存储系统,而KeyDB在100%兼容Redis API的情况下将Red...

    搜云库技术团队

扫码关注云+社区

领取腾讯云代金券