一、synchronized 修饰同步代码块
用 synchronized 修饰代码段作为同步锁,代码如下:
public class LockDemo {
public Object object = new Object();
public void show(){
synchronized (object) {
System.out.println(">>>>>>hello world!");
}
}
public static void main(String[] args) {
new LockDemo().show();
}
}
直接运行 main() 方法让 LockDemo 类编译下,然后定位到具体 LockDemo.class 类文件路径处,通过 javap
指令查看底层代码,如下:
javap -c LockDemo.class
从上图中发现底层是由 monitorenter、monitorexit 去实现同步操作的。
但是这里会发现一个 monitorenter 对应着两个 monitorexit,按理加锁和解锁不应该是两个操作么? 怎么来了两个解锁?其实这样设计是为了保证出现异常时能够把锁释放,不会让程序卡死。但是并不是一定是1:2,比如如下代码:
public class LockDemo {
public Object object = new Object();
public void show(){
synchronized (object) {
System.out.println(">>>>>>hello world!");
throw new RuntimeException(">>>>>>异常");
}
}
public static void main(String[] args) {
new LockDemo().show();
}
}
通过 javap 命令查看如下图示:
通过 athrow 一直把异常抛到最外层,然后最后 monitorexit 释放锁。总结一句话就是:synchornized 锁是一对出现的,但是为了能够在异常环境下锁能够释放,加了善尾处理。
二、synchornized 修改方法
代码如下:
public class LockDemo {
public Object object = new Object();
public void show(){
synchronized (object) {
synchronized (this) {
System.out.println(">>>>>>hello world!");
}
}
}
public static synchronized void sop() {
}
public static void main(String[] args) {
new LockDemo().show();
}
}
通过 javap -v LockDemo.class 命令查看字节码,如下图示:
提示:通过 java -v Xxx 查看到更全面的信息
方法级别的同步是隐形的,无须通过字节码指令控制,它实现在方法调用和返回操作之中。虚拟机可以从方法常量池中方法表结构中的 ACC_SYNCHRONIZED 方法标识得知这个方法是否声明为同步方法,调用时,调用指令会检查方法的 ACC_SYNCHRONIZED 访问标识符是否被设置,如果设置,执行线程就需要先成功持有 Montior
管程,然后才能够执行该方法,其他线程都无法获取到同一个管程。如果同步方法在运行期间抛出异常,且在方法内无法处理,那这个同步方法所持有的管程将在异常抛出到同步方法边界之外自动释放。
三、什么是 Monitor 监视器?
在 HotSpot 虚拟机中,Monitor 是一种用于实现同步的机制。Monitor 可以确保在多线程环境下,同一时间只有一个线程可以访问临界区(即共享资源),从而保证线程安全。
在 Java 中,每个对象都有一个 Monitor,可以通过 synchronized 关键字来获得该对象的 Monitor。当线程进入 synchronized 块时,它会尝试获取对象的 Monitor,如果该 Monitor 正在被其他线程持有,则当前线程将被阻塞,直到 Monitor 可用。当线程退出 synchronized 块时,它将释放对象的 Monitor,这样其他线程就可以获取 Monitor 并进入 synchronized 块。
以下是一个简单的示例,演示了如何使用 synchronized 关键字获取对象的 Monitor,以保证线程安全:
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized void decrement() {
count--;
}
public synchronized int getCount() {
return count;
}
}
class Main {
public static void main(String[] args) {
Counter counter = new Counter();
// 创建 1000 个线程,每个线程分别执行 1000 次增加和减少操作
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.increment();
counter.decrement();
}
}).start();
}
// 等待所有线程执行完毕
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出最终计数器的值
System.out.println(counter.getCount()); // 0
}
}
在上面的代码中,我们创建了一个名为 Counter 的类,它包含一个计数器变量 count 和三个同步方法 increment()、decrement() 和 getCount()。在这些方法中,我们使用 synchronized 关键字获取对象的 Monitor,以确保在多线程环境下对计数器的操作是线程安全的。
在主方法中,我们创建了 1000 个线程,每个线程分别执行 1000 次增加和减少操作,从而对计数器进行了大量的并发修改。最后,我们输出计数器的值,期望得到 0,以确保所有操作都是正确的。
Monitor 底层采用 ObjectMonitor.cpp jvm 实现,并且每个对象都是天生带着一个 Monitor 监视器。
看到初始化 ObjectMonitor 构造方法,源码如下图示:
属性 | 属性描述 |
---|---|
_owner | 指向持有 ObjectMonitor 对象的线程 |
-WaitSet | 存放处于 wait 状态的线程队列 |
_EntryList | 存放处于等待锁 block 状态的线程队列 |
_recursions | 锁重入次数 |
_count | 用来记录该线程获取锁的次数 |