Java 基础(3)—synchornized 关键字简单理解

一、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

在这里插入图片描述

从上图中发现底层是由 monitorentermonitorexit 去实现同步操作的。

但是这里会发现一个 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 查看到更全面的信息

扫描二维码关注公众号,回复: 16727836 查看本文章

在这里插入图片描述

方法级别的同步是隐形的,无须通过字节码指令控制,它实现在方法调用和返回操作之中。虚拟机可以从方法常量池中方法表结构中的 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 用来记录该线程获取锁的次数

在这里插入图片描述

假如一个线程A过来,进入到 EntryList 集合中,然后尝试去获取 Monitor 监视器,就是去尝试去获取锁,然后获得锁之后,就会进入 The Owner 区域,也就是表示这个线程A加锁成功,然后去调用同步方法代码。假设执行同步方法时,发现 wait 这种方法时,线程A就会进入到 WaitSet 队列中,并且 Monitor 中的 Owner 也会变成 null,重入次数 Recursions = 0,那么 EntryList 中刚过来的新线程或者是 WaitSet 中被唤醒的线程就会开始竞争 Monitor 锁。流程和线程A加锁一样。

猜你喜欢

转载自blog.csdn.net/qq_35971258/article/details/129143827