关于 java线程的 锁池、等待池(二)

围绕着:「等待池中被 "唤醒"notifyAll() 的线程一定会进入锁池吗?」

接着上篇转载的博客:https://blog.csdn.net/ScorpC/article/details/90296146

自己又继续深入研究了这个问题,查阅了java虚拟机经典书籍《Inside the Java Virtual Machine(第2版)》,瞬间懂!

不仅感叹,经典就是经典,能成为经典一定是有原因的!

也告诫自己,尤其是基础的东西,遇到问题,最好优先自己亲自查阅书籍、阅读源码、官方开发文档等

共勉!

以下内容总结自此书的:第20章 线程同步

 

 

可以在语言级支持多线程是java语言的一大优势,这种支持主要集中在同步上,或调解多个线程的活动和共享数据。Java所使用的同步机制是监视器。本章讲述了监视器,展示了java虚拟机使用它们的方式,还描述了指令集在数据的锁定和解锁方面是如何支持监视器的。

1. java中的监视器支持两种线程:互斥和协作

   java虚拟机通过对象锁来实现互斥;

   协作则是通过Object类的wait()和notify()方法来实现;

2. java虚拟机所使用的这种监视器被称作“等待并唤醒”监视器(有时也被称作“发信号并继续”监视器);

 

在这种监视器中,一个已经持有监视器的线程,可以通过执行一个等待命令,暂停自身的执行。当线程执行了等待命令后,它就会释放监视器,并进入一个等待区,这个线程会在那里一直持续暂停状态,直到一段时间后,这个监视器中的其他线程执行了唤醒命令。当一个线程执行了唤醒命令之后,它会继续持有监视器,直到它主动释放监视器,如执行了一个等待命令或者执行完监视区域。当执行唤醒的线程释放了监视器后等待线程苏醒,并重新获得监视器。

 

java虚拟机中的监视器模型如上图所示,将监视器分成了三个区域:

         中间的大方框包括一个单独的线程,是监视器的持有者;

         左边的小方框是入口;

         右边是另一个小方框是等待区;

         活动线程用深灰色圆画出;

         暂停的线程用浅灰色画出;

 

3. 上图也显示了线程与监视器交互所必须“通过”的几道门;

 

当一个线程到达监视区域的开始出时,它会通过最左边的1号门进入监视器,发现自己身处在那个叫入口区的方框中;

 

如果该没有任何线程正持有监视器,也没有其他线程正在入口区中等待,这个线程就会立刻通过下一道门:2号门,并持有监视器;

作为这个监视器的持有者,它将继续执行监视区域中的代码;

 

或者也可能出现另一种情况,已经有另一个线程正持有监视器,这个新到达的线程就必须在入口区域等待,很可能那里已经有一些线程在等待了,这个线程会被阻塞,所以不能执行监视区域中的代码;

 

如果一个监视器的持有者在它释放监视器前没有执行唤醒命令(同时在此之前也没有任何等待线程被唤醒并等待苏醒),那么位于入口区中的那些线程将会竞争获得监视器。如果上一个持有者执行了唤醒命令,入口区中的线程就不得不与一个或多个等待区中的线程来竞争。而如果是等待区中的某个线程赢了,它会通过4号门退出等待区并重新获得监视器。注意,一个线程只有通过3号门和4号门才能进入或退出等待区。一个线程只有在它正持有监视器时才能执行等待命令,而且它只能通过再次成为监视器的持有者才能离开等待区;

 

在java虚拟机中,线程在执行等待命令时可以随意指定一个暂停时间。如果一个线程指定了暂停时间,而且在暂停时间截止之前没有其他线程执行唤醒命令。这个等待线程会从虚拟机得到一个自动唤醒的命令。也就是说,在暂停时间到了以后,即使没有来自其他线程的唤醒命令,它也会自动苏醒。

 

Java虚拟机提供了两种唤醒命令:”notify”和”notify all“。notify命令随意从等待区中选择一个线程并将其标志为 "可能苏醒" ,而notify all 命令会将等待区中的所有线程都标志成 "可能苏醒"

 

Java虚拟机如何从等待区以及入口区选择下一个线程来执行,在很大程度上取决于java虚拟机的设计者;

 

总之,实现可以任意自由选择哪一个线程;

 

程序员必须不依赖任何特定的有关优先级的算法或者安排,至少在编写平台无关的java程序时应该这样;

 

比如说,因为不知道notify命令将会导致等待区中的哪一个线程苏醒,只有当绝对确认只会有一个线程在等待区中挂起的时候,才应该使用notify(相对notify all)而言。只要存在同时有多个线程在等待区中被挂起的可能性,就应该使用notify all。

 

4.对象锁

在前面的章节中提到过,java虚拟机的一些运行时数据区会被所有的线程共享,其他的数据是各个线程私有的;

 

因为方法区是被所有线程共享的,java程序需要为两种多线程访问数据进行协调:

        保存在堆中的实例变量;

        保存在方法区中的类变量;

程序不需要协调保存在java栈中的局部变量,因为java栈中的数据是属于拥有该栈的线程私有的;

 

在java虚拟机中,每个对象和类在逻辑上都是和一个监视器相关联的。对于对象来说,相关联的监视器保护对象的实例变量。对于类来说,监视器保护类的类变量。如果一个对象没有实例变量,或者一个类没有类变量,相关联的监视器就什么都不监视。

 

类锁实际上用对象锁实现:java虚拟机在加载一个class文件的时候,它会创建一个java.lang.Class类的实例。当锁住一个类的时候,实际上锁住的是那个类的Class对象。

 

一个线程可以允许多次对同一个对象上锁。对于每一个对象来说,java虚拟机维护一个计数器,记录对象被加了多少次锁。没有被锁的对象的计数器是0.但一个线程第一次获得锁的时候,计数器跳到1. 线程每加锁一次,计数器就加1。(只有已经拥有了这个对象的锁的线程才能对该对象再次加锁,在它释放锁之前,其他的线程不能对这个对象加锁)每当线程释放锁一次,计数器就减1.当计数器跳到0的时候,锁就完全释放了,其他的线程才可以使用它。

 

注意:java程序员不需要自己动手加锁,对象锁是在java虚拟机内部使用的。在java程序中,你只需要编写同步语句或者同步方法就可以标志一个监视区域。当java虚拟机运行你的程序的时候,每一次进入一个监视区域的时候,它每次都会自动锁上对象或者类。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值