可能在synchronized关键字的实现原理中,你已经知道了它的底层是使用Monitor的相关指令来实现的,但是还不清楚Monitor的具体细节。本文将让你彻底Monitor的底层实现原理。
一个管程可以被认为是一个带有特殊房间的建筑,这个特殊房间只能被一个线程占用。这个房间包含很多数据和代码。
如果一个线程要占用特殊房间(也就是红色区域),那么首先它必须在Hallway中等待。调度器基于某些规则(例如先进先出)从Hallway中取一个线程。如果线程在Hallway由于某些原因被挂起,它将会被送往等待房间(也就是蓝色区域),在一段时间后被调度到特殊房间中。
简而言之,监视器是一种监视现场访问特殊房间的设备。他能够使有且仅有一个线程访问的受保护的代码和数据。
在Java虚拟机中,每一个对象和类都与一个监视器相关联。为了实现监视器的互斥功能,锁(有时候也称为互斥体)与每一个对象和类关联。在操作系统书中,这叫做信号量,互斥锁也被称为二元信号量。
如果一个线程拥有某些数据上的锁,其他线程想要获得锁只能等到这个线程释放锁。如果我们在进行多线程编程时总是需要编写一个信号量,那就不太方便了。幸运的是,我们不需要这样做,因为JVM会自动为我们做这件事。
为了声明一个同步区域(这里意味着数据不可能被超过一个线程访问),Java提供了synchronized块和synchronized方法。一旦代码被synchronized关键字绑定,它就是一个监视器区域。它的锁将会在后面被JVM实现。
Monitor是 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者Class的锁。每一个对象都有,也仅有一个 monitor。下面这个图,描述了线程和 Monitor之间关系,以及线程的状态转换图:
进入区(Entrt Set):表示线程通过synchronized要求获取对象的锁,但并未得到。
拥有者(The Owner):表示线程成功竞争到对象锁。
等待区(Wait Set):表示线程通过对象的wait方法,释放对象的锁,并在等待区等待被唤醒。
线程状态
NEW,未启动的。不会出现在Dump中。
RUNNABLE,在虚拟机内执行的。
BLOCKED,等待获得监视器锁。
WATING,无限期等待另一个线程执行特定操作。
TIMED_WATING,有时限的等待另一个线程的特定操作。
TERMINATED,已退出的。
举个例子:
package com.jiuyan.mountain.test; import java.util.concurrent.TimeUnit; /** * Hello world! * */ public class App { public static void main(String[] args) throws InterruptedException { MyTask task = new MyTask(); Thread t1 = new Thread(task); t1.setName("t1"); Thread t2 = new Thread(task); t2.setName("t2"); t1.start(); t2.start(); } } class MyTask implements Runnable { private Integer mutex; public MyTask() { mutex = 1; } @Override public void run() { synchronized (mutex) { while(true) { System.out.println(Thread.currentThread().getName()); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
线程状态:
"t2" prio=10 tid=0x00007f7b2013a800 nid=0x67fb waiting for monitor entry [0x00007f7b17087000] java.lang.Thread.State: BLOCKED (on object monitor) at com.jiuyan.mountain.test.MyTask.run(App.java:35) - waiting to lock <0x00000007d6b6ddb8> (a java.lang.Integer) at java.lang.Thread.run(Thread.java:745) "t1" prio=10 tid=0x00007f7b20139000 nid=0x67fa waiting on condition [0x00007f7b17188000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method)
t1没有抢到锁,所以显示 BLOCKED 。t2抢到了锁,但是处于睡眠中,所以显示 TIMED_WAITING ,有限等待某个条件来唤醒。
把睡眠的代码去掉,线程状态变成了:
"t2" prio=10 tid=0x00007fa0a8102800 nid=0x6a15 waiting for monitor entry [0x00007fa09e37a000] java.lang.Thread.State: BLOCKED (on object monitor) at com.jiuyan.mountain.test.MyTask.run(App.java:35) - waiting to lock <0x0000000784206650> (a java.lang.Integer) at java.lang.Thread.run(Thread.java:745) "t1" prio=10 tid=0x00007fa0a8101000 nid=0x6a14 runnable [0x00007fa09e47b000] java.lang.Thread.State: RUNNABLE at java.io.FileOutputStream.writeBytes(Native Method)
t1显示 RUNNABLE ,说明正在运行,这里需要额外说明一下,如果这个线程正在查询数据库,但是数据库发生死锁,虽然线程显示在运行,实际上并没有工作,对于IO型的线程别只用线程状态来判断工作是否正常。
把MyTask的代码小改一下,线程拿到锁之后执行wait,释放锁,进入等待区。
public void run() { synchronized (mutex) { if(mutex == 1) { try { mutex.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
线程状态如下:
"t2" prio=10 tid=0x00007fc5a8112800 nid=0x5a58 in Object.wait() [0x00007fc59b58c000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) "t1" prio=10 tid=0x00007fc5a8111000 nid=0x5a57 in Object.wait() [0x00007fc59b68d000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method)
两个线程都显示 WAITING ,这次是无限期的,需要重新获得锁,所以后面跟了 on object monitor 。
再来个死锁的例子:
package com.jiuyan.mountain.test; import java.util.concurrent.TimeUnit; /** * Hello world! * */ public class App { public static void main(String[] args) throws InterruptedException { MyTask task1 = new MyTask(true); MyTask task2 = new MyTask(false); Thread t1 = new Thread(task1); t1.setName("t1"); Thread t2 = new Thread(task2); t2.setName("t2"); t1.start(); t2.start(); } } class MyTask implements Runnable { private boolean flag; public MyTask(boolean flag) { this.flag = flag; } @Override public void run() { if(flag) { synchronized (Mutex.mutex1) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } synchronized (Mutex.mutex2) { System.out.println("ok"); } } } else { synchronized (Mutex.mutex2) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } synchronized (Mutex.mutex1) { System.out.println("ok"); } } } } } class Mutex { public static Integer mutex1 = 1; public static Integer mutex2 = 2; }
线程状态:
"t2" prio=10 tid=0x00007f5f9c122800 nid=0x3874 waiting for monitor entry [0x00007f5f67efd000] java.lang.Thread.State: BLOCKED (on object monitor) at com.jiuyan.mountain.test.MyTask.run(App.java:55) - waiting to lock <0x00000007d6c45bd8> (a java.lang.Integer) - locked <0x00000007d6c45be8> (a java.lang.Integer) at java.lang.Thread.run(Thread.java:745) "t1" prio=10 tid=0x00007f5f9c121000 nid=0x3873 waiting for monitor entry [0x00007f5f67ffe000] java.lang.Thread.State: BLOCKED (on object monitor) at com.jiuyan.mountain.test.MyTask.run(App.java:43) - waiting to lock <0x00000007d6c45be8> (a java.lang.Integer) - locked <0x00000007d6c45bd8> (a java.lang.Integer) at java.lang.Thread.run(Thread.java:745) Found one Java-level deadlock: ============================= "t2": waiting to lock monitor 0x00007f5f780062c8 (object 0x00000007d6c45bd8, a java.lang.Integer), which is held by "t1" "t1": waiting to lock monitor 0x00007f5f78004ed8 (object 0x00000007d6c45be8, a java.lang.Integer), which is held by "t2"
这个有点像哲学家就餐问题,每个线程都持有对方需要的锁,那就运行不下去了。
We know that each object/class is associated with a Monitor. I think it is good to say that each object has a monitor, since each object could have its own critical section, and capable of monitoring the thread sequence.
To enable collaboration of different threads, Java provide wait() and notify() to suspend a thread and to wake up another thread that are waiting on the object respectively. In addition, there are 3 other versions:
wait(long timeout, int nanos) wait(long timeout) notified by other threads or notified by timeout. notify(all)
Those methods can only be invoked within a synchronized statement or synchronized method. The reason is that if a method does not require mutual exclusion, there is no need to monitor or collaborate between threads, every thread can access that method freely.
Monitors – The Basic Idea of Java Synchronization:https://www.programcreek.com/2011/12/monitors-java-synchronization-mechanism/
https://www.jianshu.com/p/c6a04c88900a
https://blog.csdn.net/Oeljeklaus/article/details/88366789
专注分享 Java、 Kotlin、Spring/Spring Boot、MySQL、redis、neo4j、NoSQL、Android、JavaScript、React、Node、函数式编程、编程思想、"高可用,高性能,高实时"大型分布式系统架构设计主题。
High availability, high performance, high real-time large-scale distributed system architecture design 。
分布式框架:Zookeeper、分布式中间件框架等
分布式存储:GridFS、FastDFS、TFS、MemCache、redis等
分布式数据库:Cobar、tddl、Amoeba、Mycat
云计算、大数据、AI算法
虚拟化、云原生技术
分布式计算框架:MapReduce、Hadoop、Storm、Flink等
分布式通信机制:Dubbo、RPC调用、共享远程数据、消息队列等
消息队列MQ:Kafka、MetaQ,RocketMQ
怎样打造高可用系统:基于硬件、软件中间件、系统架构等一些典型方案的实现:HAProxy、基于Corosync+Pacemaker的高可用集群套件中间件系统
Mycat架构分布式演进
大数据Join背后的难题:数据、网络、内存和计算能力的矛盾和调和
Java分布式系统中的高性能难题:AIO,NIO,Netty还是自己开发框架?
高性能事件派发机制:线程池模型、Disruptor模型等等。。。
合抱之木,生于毫末;九层之台,起于垒土;千里之行,始于足下。不积跬步,无以至千里;不积小流,无以成江河。