深入理解 synchronized 关键字

2023-10-27

看书的时候,看到这里,觉得有必要记录一下,那就顺手写一下。

先看一下 synchronized 的官方解释的翻译:

synchronized 关键字可以实现一个简单的策略来防止线程干扰和内存一致性错误,如果一个对象对多个线程是可见的,那么该对象的所有读或者写都将通过同步的方式来进行。

  • synchronized 提供了一种锁的机制,能够确保共享变量的互斥访问,从而防止数据不一致问题的出现。
  • synchronized 包括 monitor enter 和 monitor exit 两个 JVM 指令,它能够保证在任何时候任何线程执行到 monitor enter 成功之前都必须从主内存中获取数据,而不是在缓存中,在 monitor exit 运行成功之后,共享变量被更新后的值必须刷入主存
  • synchronized 的指令严格遵守Java happens-before 规则,一个 monitor exit 指令之前必定要有一个 monitor enter

用法的话就不多说了,两种用法,同步方法和同步代码块,可以看别的帖子了解一下。

主要研究一下 synchronized 的原理。

synchronized 提供了一种互斥机制,就是同一时刻,只能有一个线程访问同步资源。就是某个线程获取了与 synchronized(mutex) 中 mutex 关联的 monitor 锁。

先写一个简单的例子,如下

import java.util.concurrent.TimeUnit;

public class Main{

    private final static Object MUTEX = new Object();

    public static void main(String[] args) {
        final Main main = new Main();
        for (int i = 0; i < 5; i++) {
            new Thread(main::accessResource).start();
        }
    }

    public void accessResource() {
        synchronized (MUTEX) {
            try {
                TimeUnit.MINUTES.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

上边的代码定义了一个 accessResource ,并且使用了同步代码块的方式对其进行了同步,同时定义五个线程调用此方法,前边说了,使用 synchronized 关键字同步了代码块就会有互斥,所以只能有一个线程获取了 mutex monnitor 锁,其他线程只能进入阻塞状态,等待获取到 mutex monitor 锁的线程把它进行释放,先运行这个示例程序,然后打开 JConsole 工具对其进行监控,如下图
在这里插入图片描述
选择好本地的进程,然后连接就行,再切换到线程。
在这里插入图片描述
看到这里这个 thread-0 在 TIMED_WAITING 状态,而另外那四个线程都是 BLOCKED 状态。

再使用 jstack 命令打印进程的线程堆栈信息。
在这里插入图片描述
可以看到 Thread-2 持有 monitor<0x00000000d80302f0> 的锁并且处于休眠状态中,那么其他线程就没办法进入 accessResourse 方法中。其他几个线程进入 BLOCKED 状态并且等待着获取 monitor<0x00000000d80302f0> 的锁。

然后这里使用 JDK 的 javap 对这个类反汇编一下,输出 JVM 指令,指令如下:

public class test.Main {
  public test.Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class test/Main
       3: dup
       4: invokespecial #3                  // Method "<init>":()V
       7: astore_1
       8: iconst_0
       9: istore_2
      10: iload_2
      11: iconst_5
      12: if_icmpge     42
      15: new           #4                  // class java/lang/Thread
      18: dup
      19: aload_1
      20: dup
      21: invokevirtual #5                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
      24: pop
      25: invokedynamic #6,  0              // InvokeDynamic #0:run:(Ltest/Main;)Ljava/lang/Runnable;
      30: invokespecial #7                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
      33: invokevirtual #8                  // Method java/lang/Thread.start:()V
      36: iinc          2, 1
      39: goto          10
      42: return

  public void accessResource();
    Code:
       0: getstatic  //这里获取到 MUTEX     #9                  // Field MUTEX:Ljava/lang/Object;
       3: dup
       4: astore_1
       5: monitorenter  //执行 monitorenter 指令
       6: getstatic  //获取静态属性     #10                 // Field java/util/concurrent/TimeUnit.MINUTES:Ljava/util/concurrent/TimeUnit;
       9: ldc2_w        #11                 // long 10l
      12: invokevirtual #13                 // Method java/util/concurrent/TimeUnit.sleep:(J)V
      15: goto  //休眠之后跳转到23行          23
      18: astore_2
      19: aload_2
      20: invokevirtual #15                 // Method java/lang/InterruptedException.printStackTrace:()V
      23: aload_1  //跳转到这里
      24: monitorexit  //执行monitorexit指令
      25: goto          33
      28: astore_3
      29: aload_1
      30: monitorexit
      31: aload_3
      32: athrow
      33: return
    Exception table:
       from    to  target type
           6    15    18   Class java/lang/InterruptedException
           6    25    28   any
          28    31    28   any

  static {};
    Code:
       0: new           #16                 // class java/lang/Object
       3: dup
       4: invokespecial #1                  // Method java/lang/Object."<init>":()V
       7: putstatic     #9                  // Field MUTEX:Ljava/lang/Object;
      10: return
}

现在就可以发现 monitor enter 和 monitor exit 指令了,可以看到每一个 monitor exit 之前必有对应的 monitor enter 。

Monitorenter

每个对象都与一个 monitor 相关联,每一个 monitor 的 lock 锁只能被一个线程在同一时间获得,在一个线程尝试获得与对象关联 monitor 的所有权时会发生如下的几个情况。

  • 如果 monitor 的计数器为0,则意味着该 monetor 的 lock 还没有被获得,某个线程获得之后立即对该计数器加一,意味着这个线程就是这个 monitor 的持有者了。
  • 如果一个已经拥有该 monitor 所有权的线程重入,则会导致 monitor 计数器再次累加。
  • 如果 monitor 已经被其他线程所拥有,则其他线程尝试获取该 monitor 的所有权时,就会被陷入到阻塞状态,直到 monitor 计数器变为0,才能再次尝试获取对 monitor 的所有权。

Monitorexit

释放对 monitor 的所有权,想要释放对某个对象关联的 monitor 的所有权的前提是,曾经获得了所有权。释放的过程比较简单,就是将 monitor 的计数器减一,如果计数器的结果为0,那就意味着该线程不再拥有对该 monitor 的所有权,通俗地讲就是解锁。与此同时被该 monitor block 的线程讲再次尝试获得对该 monitor 的所有权。

以上就是 synchronized 的一个简单原理了,接着再看一下 This Monitor 和 Class Monitor 的一个比较详细的解释。

this monitor

下面看个例子

import java.util.concurrent.TimeUnit;

/**
 * @author hasaki_w_c
 */
public class ThisMonitor{
    public static void main(String[] args) {
        ThisMonitor thisMonitor = new ThisMonitor();
        new Thread(thisMonitor::method1, "thread1").start();
        new Thread(thisMonitor::method2, "thread2").start();
    }

    public synchronized void method1() {
        System.out.println(Thread.currentThread().getName() + "method1");
        try {
            TimeUnit.MINUTES.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void method2() {
        System.out.println(Thread.currentThread().getName() + "method2");
        try {
            TimeUnit.MINUTES.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

比如这个代码中,两个 method 方法都被 synchronized 关键字修饰,启动两个线程分别访问这两个方法,我们看一下这两个方法对应的 monitor 是什么,是不是一致的。

运行一下程序,看一下输出,只输出了一个,另一个没有调用。
在这里插入图片描述
去看一下线程的堆栈信息,用 jstack pid 命令看一下子,可以看到 thread1 获取了<0x00000000d618a428> monitor 的 lock 并处于休眠状态,而 thread2 线程想要获取到 <0x00000000d618a428>monitor 的 lock 时,陷入了 BLOCKED 状态。
在这里插入图片描述
由此可见,使用 synchronized 关键字同步类的不同示例方法,竞争的是同一个 monitor 的锁,而与之关联的则是 ThisMonitor 的实例引用,我们可以讲上边这个代码改一下,把 method2 的同步方法改成使用 this 的同步代码块。

import java.util.concurrent.TimeUnit;

/**
 * @author hasaki_w_c
 */
public class ThisMonitor{
    public static void main(String[] args) {
        ThisMonitor thisMonitor = new ThisMonitor();
        new Thread(thisMonitor::method1, "thread1").start();
        new Thread(thisMonitor::method2, "thread2").start();
    }

    public synchronized void method1() {
        System.out.println(Thread.currentThread().getName() + "method1");
        try {
            TimeUnit.MINUTES.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void method2() {
        synchronized (this) {
            System.out.println(Thread.currentThread().getName() + "method2");
            try {
                TimeUnit.MINUTES.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

然后运行一下,发现与前边同步方法的结果是一样的,所以便证实了而与之关联的则是 ThisMonitor 的实例引用。下面再来看看 class monitor。

class monitor

和上边方法一样,代码稍微改一下,如下:

import java.util.concurrent.TimeUnit;

/**
 * @author hasaki_w_c
 */
public class ClassMonitor {
    public static void main(String[] args) {
        new Thread(ClassMonitor::method1, "thread1").start();
        new Thread(ClassMonitor::method2, "thread2").start();
    }

    public static synchronized void method1() {
        System.out.println(Thread.currentThread().getName() + "method1");
        try {
            TimeUnit.MINUTES.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static synchronized void method2() {
        System.out.println(Thread.currentThread().getName() + "method2");
        try {
            TimeUnit.MINUTES.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果也与上边一样,同一时刻也是只能有一个线程访问 ClassMonitor 的静态方法,再看看线程堆栈信息。
在这里插入图片描述
thread1 线程持有 <0x00000000d6187b88> monitor 的锁,正在休眠,而 thread2线程试图获取<0x00000000d6187b88> monitor锁的时候陷入 BLOCKED状态。因此用 synchronized 同步某个类的不同静态方法争抢的也是同一个 monitor 的 lock,与前边this monitor不同的地方就在与锁后边是a java.lang.Class for xxx ,所以与该 monitor 关联的是 ClassMonitor.class 实例。
与上边同样修改一下代码

import java.util.concurrent.TimeUnit;

/**
 * @author hasaki_w_c
 */
public class ClassMonitor {
    public static void main(String[] args) {
        new Thread(ClassMonitor::method1, "thread1").start();
        new Thread(ClassMonitor::method2, "thread2").start();
    }

    public static synchronized void method1() {
        System.out.println(Thread.currentThread().getName() + "method1");
        try {
            TimeUnit.MINUTES.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void method2() {
        synchronized (ClassMonitor.class) {
            System.out.println(Thread.currentThread().getName() + "method2");
            try {
                TimeUnit.MINUTES.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

这个使用的就是ClassMonitor.class 的实例引用作为 monitor 。
以上就是我最近看书学到的一些关于 synchronized 的东西,在此做一记录。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深入理解 synchronized 关键字 的相关文章

随机推荐

  • MySQL 数据库 ERROR 1045 (28000): Access denied for user ‘root‘@‘localhost‘ (using password: NO)

    我的数据库版本是 8 0 28 今天连接数据库的时候出现了这个错误 通过 mysql u root p 命令进入数据库 让输入密码 密码忘了就会提示这个 ERROR 1045 28000 Access denied for user roo
  • 【Python蒙特卡罗算法】

    蒙特卡罗算法 1 前言 2 伪随机数生成器 PRNG 2 1 线性同余发生器 LCG 2 2 逆变换采样 2 3 Python中的随机数生成器 3 蒙特卡罗积分 3 1 有限积分 3 2 方差估计 3 3 方差缩减 3 4 无穷积分 3 5
  • Docker搭建RabbitMQ集群_开启MQTT插件后连接不上

    RabbitMQ集群搭建和测试总结 亲测 Docker安装RabbitMQ集群 亲测成功 开启MQTT插件后 集群方式连接不上 看日志报错连接超时 执行如下命令就可以连接上了 rabbitmqctl eval ra overview rab
  • c++实现Qt信号和槽机制

    文章目录 简介 信号 槽 信号与槽的连接 特点 观察者模式 定义 观察者模式结构图 实现简单的信号和槽 简介 信号槽机制与Windows下消息机制类似 消息机制是基于回调函数 Qt中用信号与槽来代替函数指针 使程序更安全简洁 信号和槽机制是
  • fmincon求解函数极值

    clear close all options fun1 46 971 5 094 x 1 80 234 x 2 0 173 x 1 2 46 994 x 2 2 3 695e 5 x 3 2 3 056 x 1 x 2 0 001 x 1
  • windows 下C++生成Dump调试文件与分析

    目录 1 前言 2 依赖库下载 3 项目配置 3 1 设置输出路径 3 2 拷贝依赖资源 3 3 将dbghelp h添加在工程中 3 4 配置lib文件路径 3 5 添加生成minidump文件方法 4 测试效果 5 打开dump文件进行
  • 提升网络安全防御能力的几个方面

    提升网络安全防御能力对于个人和组织来说都至关重要 网络安全是一个全面的概念 包括保护个人信息 防止恶意攻击和确保网络资源的安全 在这篇文章中 我将介绍几个方面来提高网络安全防御能力其中包括IP地址查询 首先 IP地址查询是一种网络安全工具可
  • C均值(K-means)聚类算法 实验

    文章目录 一 实验目的 二 实验原理 三 实验内容 四 实验步骤 1 1 随机创建100个样本的二维数据作为训练集并画出训练样本的散点图 1 2 3 进行聚类并画出聚类结果的散点图 2 1 导入iris数据集数据 2 2 3 进行聚类并画出
  • ROS系统开发——ROS,realsense风险和解决方案备忘录

    未能确认原因的重大风险问题 开启4个相机时 有时候会出现只能打开2个 或3个相机的情况 还有一个相机无法开启 2021 3 23 详细现象 长时间测试4个realsense相机过程中 使用roslaunch命令同时开启4个相机时 有时候随机
  • 【List】类型检查

    public static
  • @TableId(value = “id“,type = IdType.AUTO) 设置后无效的解决办法

    TableId value id type IdType AUTO TableId value id type IdType INPUT 刚开始自增一直是32位的 TableId value id type IdType AUTO priv
  • 如何查看中科院分区

    中科院分区和JCR分区查询 jcr分区查询官网 xing meng的博客 CSDN博客
  • 如何让自动化测试框架更自动化?

    一 引言 对于大厂的同学来说 接口自动化是个老生常谈的话题了 毕竟每年的MTSC大会议题都已经能佐证了 不是大数据测试 就是AI测试等等 越来越高大上了 不可否认这些专项的方向是质量智能化发展的方向 但是凡事都遵循2 8定律 80 的从事软
  • oracle中exp/imp命令详解

    ORACLE数据库有两类备份方法 第一类为物理备份 该方法实现数据库的完整恢复 但数据库必须运行在归挡模式下 业务数据库在非归挡模式下运行 且需要极大的外部存储设备 例如磁带库 第二类备份方式为逻辑备份 业务数据库采用此种方式 此方法不需要
  • 使用OpenCV+Python进行图像处理的初学者指南

    点击上方 小白学视觉 选择加 星标 或 置顶 重磅干货 第一时间送达 介绍 我们都知道一句话 每张照片都可以告诉我们一个故事 图像中可能隐藏着很多信息 我们可以用不同的方式和视角来解释它 那么 什么是图像 如何处理图像 简而言之 我们可以说
  • 类加载机制—详解

    1 类加载 class 文件中都是一个一个的二进制 通过前面个两个字节进行判断 2 双亲委托机制 class 文件通过类加载器进入到 JVM虚拟机中运行 2 1类加载器 类加载器分为两种 一种是引导类加载器 启动类加载器是已经提供好的 一种
  • 世间万物,音乐不可辜负

    世间万物 唯有爱不可辜负 爱 除了来自家人的亲情 恋人的爱情 朋友的友情 爱 还来自你对世间万物的感受 比如 美食 通过嗅觉 品尝到美味 又比如音乐 通过听觉 调动你的情绪 激发你的想象力 共情能力 愉悦你的身心 安慰你 鼓励你 今天 跟大
  • 大数据实战 Linux Ubuntu 20.04.1 server 最小化安装及其网络配置

    1 Uduntu 的诞生 Ubuntu是一个以桌面应用为主的Linux操作系统 其名称来自非洲南部祖鲁语或豪萨语的 ubuntu 一词 意思是 人性 我的存在是因为大家的存在 是非洲传统的一种价值观 buntu Linux是由南非人马克 沙
  • 【Linux篇】fwrite函数

    include
  • 深入理解 synchronized 关键字

    看书的时候 看到这里 觉得有必要记录一下 那就顺手写一下 先看一下 synchronized 的官方解释的翻译 synchronized 关键字可以实现一个简单的策略来防止线程干扰和内存一致性错误 如果一个对象对多个线程是可见的 那么该对象