本文主要是介绍关于 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虚拟机运行你的程序的时候,每一次进入一个监视区域的时候,它每次都会自动锁上对象或者类。
这篇关于关于 java线程的 锁池、等待池(二)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!