wait()与notify()获取对象监视器(锁)的三种方式_守仁的博客-程序员宅基地

技术标签: Java  

在某个线程方法中对wait()和notify()的调用必须指定一个Object对象,而且该线程必须拥有该Object对象的monitor。而获取对象monitor最简单的办法就是,在对象上使用synchronized关键字。当调用wait()方法以后,该线程会释放掉对象锁,并进入sleep状态。而在其它线程调用notify()方法时,必须使用同一个Object对象,notify()方法调用成功后,所在这个对象上的相应的等侍线程将被唤醒。
对于被一个对象锁定的多个方法,在调用notify()方法时将会任选其中一个进行唤醒,而notifyAll()则是将其所有等待线程唤醒。
package  net.mindview.util;

import  javax.swing.JFrame;

public  class  WaitAndNotify {
        public  static  void  main(String[] args) {
            System.  out .println( "Hello World!"  );
            WaitAndNotifyJFrame frame =  new  WaitAndNotifyJFrame();
            frame.setDefaultCloseOperation(JFrame.  EXIT_ON_CLOSE );
              // frame.show();
            frame.setVisible(  true );
      }
}

@SuppressWarnings ( "serial"  )
class  WaitAndNotifyJFrame  extends  JFrame {

        private  WaitAndNotifyThread  t  ;

        public  WaitAndNotifyJFrame() {
            setSize(300, 100);
            setLocation(250, 250);
            JPanel panel =  new  JPanel();
            JButton start =  new  JButton( new  AbstractAction( "Start" ) {
                    public  void  actionPerformed(ActionEvent event) {
                          if  ( t  ==  null ) {
                                t  =  new  WaitAndNotifyThread(WaitAndNotifyJFrame. this );
                                t .start();
                        }  else  if  ( t  . isWait  ) {
                                t .  isWait  =  false  ;
                                t .n();
                                // t.notify();
                        }
                  }
            });
            panel.add(start);
            JButton pause =  new  JButton( new  AbstractAction( "Pause" ) {
                    public  void  actionPerformed(ActionEvent e) {
                          if  ( t  !=  null ) {
                                t .  isWait  =  true  ;
                        }
                  }
            });
            panel.add(pause);
            JButton end =  new  JButton( new  AbstractAction( "End" ) {
                    public  void  actionPerformed(ActionEvent e) {
                          if  ( t  !=  null ) {
                                t .interrupt();
                                t  =  null ;
                        }
                  }
            });
            panel.add(end);
            getContentPane().add(panel);
      }

}

@SuppressWarnings ( "unused"  )
class  WaitAndNotifyThread  extends  Thread {

        public  boolean  isWait  ;
        private  WaitAndNotifyJFrame  control  ;
        private  int  count  ;

        public  WaitAndNotifyThread(WaitAndNotifyJFrame f) {
              control  = f;
              isWait  =  false  ;
              count  = 0;
      }

        public  void  run() {
              try  {
                    while  ( true  ) {
                          synchronized  ( this  ) {
                              System.  out .println( "Count:"  +  count ++);
                               sleep(100);
                                if  ( isWait  )
                                    wait();
                        }
                  }
            }  catch  (Exception e) {
            }
      }
       
      public   void  n() {
              synchronized  ( this  ) {
                  notify();
            }
      }

}
如上面例子方框中的代码,若去掉同步代码块,执行就会抛出 java.lang.IllegalMonitorStateException 异常。
查看JDK,我们可以看到,出现此异常的原因是 当前线程不是此对象监视器的所有者

此方法只应由作为此对象监视器的所有者的线程来调用,通过以下三种方法之一,可以使线程成为此对象监视器的所有者:
1,通过执行此对象的同步实例方法,如:
             public  synchronized  void  n() {
          notify();
      }
2,通过执行在此对象上进行同步的synchronized语句的正文,如:
       public  void  n() {
           synchronized  ( this  ) {
              notify();
          }
      }
3,对于Class类型的对象,可以通过执行该类的同步静态方法。
在调用静态方法时,我们并不一定创建一个实例对象。因此,就不能使用this来同步静态方法,所以必须使用Class对象来同步静态方法,由于notify()方法不是静态方法,所以我们无法将n()方法设置成静态方法,所以采用另外一个例子加以说明:
public  class  SynchronizedStatic  implements  Runnable {

        private  static  boolean  flag  =  true ;

//类对象同步方法一:       
      // 注意static修饰的同步方法,监视器:SynchronizedStatic.class
        private  static  synchronized  void  testSyncMethod() {
              for  ( int  i = 0; i < 100; i++) {
                    try  {
                        Thread. sleep(100);
                  }  catch  (InterruptedException e) {
                        e.printStackTrace();
                  }
                  System.  out .println( "testSyncMethod:"  + i);
            }
      }


//类对象同步方法二:       
             private  void  testSyncBlock() {
             // 显示使用获取class做为监视器.它与static  synchronized  method隐式获取class监视器一样.
              synchronized  (SynchronizedStatic.  class ) {
                    for  ( int  i = 0; i < 100; i++) {
                          try  {
                              Thread. sleep(100);
                        }  catch  (InterruptedException e) {
                              e.printStackTrace();
                        }
                        System.  out .println( "testSyncBlock:"  + i);
                  }
            }
      }


        public  void  run() {
              // flag是static的变量.所以,不同的线程会执行不同的方法,只有这样才能看到不同的锁定效果.
              if  ( flag  ) {
                    flag  =  false  ;
                   testSyncMethod();
            }  else  {
                    flag  =  true  ;
                  testSyncBlock();
            }
      }

        public  static  void  main(String[] args) {
            ExecutorService exec = Executors. newFixedThreadPool(2);
            SynchronizedStatic rt =  new  SynchronizedStatic();
            SynchronizedStatic rt1 =  new  SynchronizedStatic();
            exec.execute(rt);
            exec.execute(rt1);
            exec.shutdown();
      }
}
以上代码的运行结果是,让两个同步方法同时打印从0到99这100个数,其中方法一是一个静态同步方法,它的作用域为类;方法二显示的声明了代码块的作用域是类。这两个方法的异曲同工的。由于方法一和方法二的作用域同为类,所以它们两个方法间是互斥的,也就是说,当一个线程调用了这两个方法中的一个,剩余没有调用的方法也会对其它线程形成阻塞。因此,程序的运行结果会是:
testSyncMethod:0
testSyncMethod:1
... ...
testSyncMethod:99
testSyncBlock:0
... ...
testSyncBlock:99

但是,如果我们将方法二中的 SynchronizedStatic.  class 替换成this的话,由于作用域的没,这两个方法就不会形成互斥,程序的输出结果也会交替进行,如下所示:
testSyncBlock:0
testSyncMethod:0
testSyncBlock:1
testSyncMethod:1
... ...
testSyncMethod:99
testSyncBlock:99

由上一篇博文,我们已经说明,锁(lock)的作用域有两种,一种是类的对象,另一种的类本身。在以上代码中给出了两种使锁的作用范围为类的方法,这样就可以使同一个类的不同对象之间也能完成同步。

总结以上,需要注意的有以下几点:
1,wait()、notify()、notifyAll()都需要在拥有对象监视器的前提下执行,否则就会抛出 java.lang.IllegalMonitorStateException 异常。
2,多个线程可以同时在一个对象上等待。
3, notify()是随机唤醒一个在对象上等待的线程,若没有等待的线程,则什么也不做。
4, notify()唤醒的线程,并不是在 notify()执行以后就立即唤醒,而是在 notify()线程释放了对象监视器之后才真正执行被唤醒的线程。
5,Object的这些方法与Thread的sleep、interrupt方法相差还是很远的,不要混为一谈。





参考资料:
jdk中文文档

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/welcome000yy/article/details/8950162

智能推荐

SharePoint 2013 安装配置(1)-程序员宅基地

在这篇文章中,我将逐步介绍在Windows Server 2012 R2上安装SharePoint 2013。 在进一步详细介绍之前,让我们先了解SharePoint 2013安装的硬件和软件要求。您可以点击此处了解此安装的完整系统要求。后期我也会给大家详细介绍。环境要求:Windows 2012 Server Release Candidate running Active Dire...

数仓 - 生命周期、用户价值、忠诚度、活跃度计算_用户数据活跃度算法-程序员宅基地

1. 生命周期根据用户自注册为会员后,距今的时间段内订单量的变化判断用户的生命周期阶段。此标签有助于判断网站用户的流失情况,而且对于不同的阶段,应该采取不同的营销策略。划分标准如下:1---考察阶段:最近30天注册未下单用户;2---形成阶段-未复购-1:.最早一次销售日期>=系统日期-30天,且最早一次销售日期=最近一次销售日期;(最近30天完成了第一次购买,但是还没有再次购买)3---形成阶段-未复购-2:系统日期-90天<=最早一次销售日期<系统日期-30天,且最早一次_用户数据活跃度算法

pycharm解决光标变粗,关闭改写模式_怎么pycharm改写模式去掉_happylife_mini的博客-程序员宅基地

在pycharm用鼠标一拖中总是遇到光标变粗的问题,点击insert可以解决,但是每次点击会比较麻烦,所以可以直接关闭改写模式,即关闭tools->Vim Emulator即可。_怎么pycharm改写模式去掉

兰州驾照科目二考试心得-程序员宅基地

本人4月23、24号在兰州某驾校504练习科目二考试项目二天,25号前往考场熟悉场地,转了6圈。26号参加考试,考分90,顺利通过。看到很多学员 几次考试不过,有些放弃,有些甚至为此流泪,花时耗钱没有成果。驾校说他们通过率为35%,为此下面将个人考试心得写出来,或对些许人有用,若是将不胜欣 慰。  一、考试前进入场地时需要调整心理状态,切忌高度紧张,我在考试时候亲眼看见有些人上车不系保险带,直接将...

高德新版全类别AOI采集与分析_高德aoi_QQ_2801061513的博客-程序员宅基地

AOI(Area of Interest),顾名思义,指的是互联网电子地图中的兴趣面,同样包含四项基本信息,主要用于在地图中表达区域状的地理实体,如一个居民小区、一所大学、一个写字楼、一个产业园区、一个综合商场、一个医院、一个景区或一个体育馆等等;有2022百度poi;交通网络分析,计算公交线路及站点的服务范围与服务功能,使用AOI数据可以准确的计算出公交线路及站点的服务范围,将线路轨迹、站点分布与AOI矢量数据相叠加,可以精确计算出小区的覆盖程度,还可以根据服务的功能区分辨出社区线路、通勤线路等。_高德aoi

今日推荐课程:机器学习极简入门-程序员宅基地

课程介绍本达人课针对机器学习初学者,从机器学习、深度学习最基本的原理及学习意义入手,以模型为驱动,带领大家吃透几个最经典的机器学习模型——学习这些模型的原理、数学推导、训...

随便推点

关于KEIL MDK调试ARM程序不能仿真的问题-程序员宅基地

在单片机程序调试过程中,由于程序量小,利用仿真器进行仿真调试方便直观,所以一般经常使用。但是keil经常会出现罢工,无法用仿真器调试的现象,如下图:解决方法也很简单,按照下图设置即可:

期刊分区常识-程序员宅基地

作为一个科研工作者,了解期刊论文的一些基本常识是大有裨益的。这对于我们深入了解所从事领域的研究、和论文的写作与发表等都会有很大的帮助。比如对于期刊分区的问题,从事科研的人都听过,但是也许你并没有深入了解其概念和意义。尤其对于刚刚步入科研领域和外行的人来说,可能仅仅凭借分区和影响因子来评价期刊的优劣。如果这样的话,难免被内行人笑话。这篇文章着重讨论一下论文分区的问题并科普一些有关的基本常识。..._期刊分区

使用Matplotlib简单作图案例-程序员宅基地

文章目录一:简介二:示例三:存在的问题1:中文乱码问题2:负号不能正常显示一:简介Matplotlib 是一个 Python 的 2D绘图库,它以各种硬拷贝格式和跨平台的交互式环境生成出版质量级别的图形。二:示例创建一个简单的使用matplotlib作图示例mport matplotlib.pyplot as pltimport numpy as np# np 生成200个-1到1之间均匀的数字x = np.linspace(-1,1,200)y1 = x*2y2 = x**2# fi

SpringMVC ----从 配置文件开始-程序员宅基地

说起spring mvc,还是自己在面试之前积累的一些知识。根本不知道它真正的是什么。看过一些讲原理的书,看过一些人的博客,但这些都是为了面试而准备,自己说的非常溜,说起来一套一套的也只是暂时的。四个月的实习归来,回想起什么是spring mvc?脑海中记忆最深刻的,竟然是自己在实习期的项目中的运用----通过自己边敲代码,一遍和配置文件作战(出错一般都是配置文件出错),一边总结得到的一些知识

Map以及HashMap-程序员宅基地

  本文主要介绍java集合框架的Map集合,在日常生活中Map的运用也十分广泛。  与List集合、Set集合隶属于Collection不同,Map是一个独立的接口,与Collection相同级别的接口。  重要的是,Map集合提供了一个不一样的元素存储方法,利用“key—value”的形式进行存储。其中,每个键映射一个值。而在Set集合中,元素的存储就是利用Map的这一特性来实现。..._putall(ava.util,map<?extends jacv</div>

机器视觉 第三节 OpenCV入门初步_opcv入门机器视觉-程序员宅基地

《CSDN 人工智能学习笔记》第一部分 机器视觉 第三节 OpenCV入门初步图像读取与显示MatlabPythonC++图像平滑MatlabPythonC++声明:本系列博客为本人学习CSDN人工智能课程的学习笔记,仅供学习交流使用。这里引用百度百科关于OpenCV的描述给大家简单说明一下OpenCV:“OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库,可以运行在Linux..._opcv入门机器视觉