我们知道,多个线程争抢同一个 monitor 的 lock 会陷入阻塞进而达到数据同步、资源同步的目的,而大家可能比较疑惑,这个monitor到底是啥?
this monitor
在下面的代码 ThisMonitor 中,两个方法 methodl 和 method2 都被 synchronized 关键字修饰,启动了两个线程分别访问 method1 和 method2。
public class ThisMonitor {
public synchronized void method1() {
System.out.println(Thread.currentThread().getName() + " enter to method1");
try {
TimeUnit.MINUTES.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void method2() {
System.out.println(Thread.currentThread().getName() + " enter to method2");
try {
TimeUnit.MINUTES.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ThisMonitor thisMonitor = new ThisMonitor();
new Thread(thisMonitor::method1, "T1").start();
new Thread(thisMonitor::method2, "T2").start();
}
}
我将重点的地方用红色的框标识了出来,T1 线程获取了<0x00000000d60ffe30> monitor 的 lock 并且处于休眠状态,而 T2 线程企图获取<0x00000000d60ffe30> monitor 的 lock 时陷入了 BLOCKED 状态,可见使用 synchronized 关键字同步类的不同实例方法, 争抢的是同一个 monitor 的 lock,而与之关联的引用则是 ThisMonitor 的实例引用。
public class ThisMonitor02 {
private final Object MONITOR = new Object();
public synchronized void method1() {
System.out.println(Thread.currentThread().getName() + " enter to method1");
try {
TimeUnit.MINUTES.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void method2() {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + " enter to method2");
try {
TimeUnit.MINUTES.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ThisMonitor02 thisMonitor = new ThisMonitor02();
new Thread(thisMonitor::method1, "T1").start();
new Thread(thisMonitor::method2, "T2").start();
}
}
现在我们可以确定,与之关联的引用则是ThisMonitor 的实例引用。其中,method1 保持方法同步的方式, method2 则采用了同步代码块的方式,并且使用的是 this 的 monitor,运行修改后的代码将会发现效果完全一样。查看文档可知:当线程调用一个同步方法时,它会自动获取该方法对象的内部锁,并在该方法返回时释放锁。即使返回是由未捕获的异常引起的,也会发生锁释放。
class monitor
public class ClassMonitor {
public synchronized static void method1() {
System.out.println(Thread.currentThread().getName() + " enter to method1");
try {
TimeUnit.MINUTES.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized static void method2() {
System.out.println(Thread.currentThread().getName() + " enter to method1");
try {
TimeUnit.MINUTES.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Thread(ClassMonitor::method1, "T1").start();
new Thread(ClassMonitor::method2, "T2").start();
}
}
运行上面的代码,在同一时刻只能有一个线程访问 ClassMonitor 的静态方法,我们仍旧使用 jstack 命令分析其线程堆栈信息,如图所示。
将关键的地方用红色方框标识了出来,T1 线程持有<0x000000071118ed60> monitor 的锁在正在休眠,而 T2 线程在试图获取<0x000000071118ed60> monitor 锁的 时候陷入了 BLOCKED 状态,因此我们可以得出用 synchronized 同步某个类的不同静态方法争抢的也是同一个 monitor 的 lock,再仔细对比堆栈信息会发现与this monitor中关于 monitor 信 息 不 一 样 的 地 方 在 于 (a java.lang.Class for com.bjsxt.chapter05.demo05.ClassMonitor),由此可以推断与该 monitor 关联的引用是 ClassMonitord.class 实例。
public class ClassMonitor02 {
public synchronized static void method1() {
System.out.println(Thread.currentThread().getName() + " enter to method1");
try {
TimeUnit.MINUTES.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void method2() {
synchronized (ClassMonitor02.class) {
System.out.println(Thread.currentThread().getName() + " enter to method2");
try {
TimeUnit.MINUTES.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new Thread(ClassMonitor02::method1, "T1").start();
new Thread(ClassMonitor02::method2, "T2").start();
}
}
其中静态方法 method1继续保持同步方法的方式,而 method2 则修改为同步代码块的方式,使用 ClassMonitor.class 的实例引用作为 monitor。查看文档可知:因为静态方法是与类而不是对象相关联。在这种情况下,线程获得与类关联的类对象的内在锁。
总结
看完上面的文章之后,我总结两点。第一,我们在使用this monitor锁对象的时候,在同一类里面,定义多个同步方法,一定要注意锁对象是谁,因为如果是同一个锁对象,那么你执行两个方法的时候,只会执行一个,另外一个不会执行。第二,我们在使用class monitor的时候,也要注意锁的类对象,如果在同一个类中,多个同步方法之间,如果是同一个锁,那么执行的时候,也不会同时执行。所以希望大家理解锁对象。