iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Java多线程并发synchronized 关键字
  • 235
分享到

Java多线程并发synchronized 关键字

2024-04-02 19:04:59 235人浏览 薄情痞子

Python 官方文档:入门教程 => 点击学习

摘要

目录基础修饰普通方法修饰静态方法Synchronized 加锁原理monitorentermonitorexitsynchronized 修饰静态方法优点、缺点及优化其他说明基础 J

基础

Java 在虚拟机层面提供了 synchronized 关键字供开发者快速实现互斥同步的重量级锁来保障线程安全

synchronized 关键字可用于两种场景:

  • 修饰方法。
  • 持有一个对象,并执行一个代码块。

而根据加锁的对象不同,又分为两种情况:

  • 对象锁
  • 类对象锁

以下代码示例是 synchronized 的具体用法:

1. 修饰方法
synchronized void function() { ... }
2. 修饰静态方法
static synchronized void function() { ... }
3. 对对象加锁
synchronized(object) {
    // ...
}

修饰普通方法

synchronized 修饰方法加锁,相当于对当前对象加锁,类 A 中的 function() 是一个 synchronized 修饰的普通方法:

class A {
    synchronized void function() { ... }
}

它等效于:

class A {
    void function() { 
        synchronized(this) { ... }
    }
}

结论synchronized 修饰普通方法,实际上是对当前对象进行加锁处理,也就是对象锁。

修饰静态方法

synchronized 修饰静态方法,相当于对静态方法所属类的 class 对象进行加锁,这里的 class 对象是 JVM 在进行类加载时创建的代表当前类的 java.lang.Class 对象,每个类都有唯一的 Class 对象。这种对 Class 对象加锁,称之为类对象锁

类加载阶段主要做了三件事情:

根据特定名称查找类或接口类型的二进制字节流。

将这个二进制字节流所代表的静态存储结构转化为方法区的运行时数据结构

在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。

class A {
    static synchronized void function() { ... }
    // 相当于对 class 对象加锁,这里只是描述,静态方法和普通方法不可等效。
    void function() {
        synchronized(A.class) { ... }
    }
}

也就是说,如果一个普通方法中持有了 A.class ,那么就会与静态方法 function() 互斥,因为本质上它们加锁的对象是同一个。

Synchronized 加锁原理

public class Sync {
    Object lock = new Object();
    public void function() {
        synchronized (lock) {
            System.out.print("lock");
        }
    }
}

这是一个简单的 synchronized 关键字对 lock 对象进行加锁的 demo ,经过javac Sync.java 命令反编译生成 class 文件,然后通过 javap -verbose Sync 命令查看内容:

  public void function();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: getfield      #7                  // Field lock:Ljava/lang/Object;
         4: dup
         5: astore_1
         6: monitorenter                      // 【1】
         7: getstatic     #13                 // Field java/lang/System.out:Ljava/io/PrintStream;
        10: ldc           #19                 // String lock
        12: invokevirtual #20                 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
        15: aload_1
        16: monitorexit                       // 【2】
        17: Goto          25
        20: astore_2
        21: aload_1
        22: monitorexit                       // 【3】
        23: aload_2
        24: athrow
        25: return

【1】与【2】处的 monitorenter 和 monitorexit 两个指令就是加锁操作的关键。

而【3】处的 monitorexit ,是为了保证在同步代码块中出现 Exception 或者 Error 时,通过调用第二个monitorexit 指令来保证释放锁。

monitorenter 指令会让对象在对象头中的锁计数器计数 + 1, monitorexit 指令则相反,计数器 - 1。

monitor 锁的底层逻辑

对象会关联一个 monitor ,monitorenter 指令会检查对象是否管理了 monitor 如果没有创建一个 ,并将其关联到这个对象。

monitor 内部有两个重要的成员变量 owner(拥有这把锁的线程)和 recursions(记录线程拥有锁的次数),当一个线程拥有 monitor 后其他线程只能等待。

加锁意味着在同一时间内,对象只能被一个线程获取到。

monitorenter

monitorenter 指令标记了同步代码块的开始位置,也就是这个时候会创建一个 monitor ,然后当前线程会尝试获取这个 monitor 。

monitorenter 指令触发时,线程尝试获取 monitor 锁有三种逻辑:

  • monitor 锁计数器为 0 ,意味着目前还没有被任意线程持有,那这个线程就会立刻持有这个 monitor 锁,然后把锁计数器+1,一旦+1,别的线程再想获取,就需要等待。
  • 如果又对当前对象执行了一个 monitorenter 指令,那么对象关联的 monitor 已经存在,就会把锁计数器 + 1,锁计数器的值此时是 2,并且随着重入的次数,会一直累加。
  • monitor 锁已被其他线程持有,锁计数器不为 0 ,当前线程等待锁释放。

monitorexit

monitorexit 指令会对锁计数器进行 - 1 ,如果在执行 - 1 后锁计数器仍不为 0 ,持有锁的线程仍持有这个锁,直到锁计数器等于 0 ,持有线程才释放了锁。

任意线程访问加锁对象时,首先要获取对象的 monitor ,如果获取失败,该现场进入阻塞状态,即 Blocked。当这个对象的 monitor 被持有线程释放后,阻塞等待的线程就有机会获取到这个 monitor 。

synchronized 修饰静态方法

根据锁计数器的原理,理论上说, monitorenter 和 monitorexit 两个指令应该成对出现(抛除处理 Exception 或 Error 的 monitorexit)。重复对同一个线程进行加锁。

我们来写一个示例检查一下:

public class Sync {
    Object lock = new Object();
    public void function() {
        synchronized (Sync.class) {
            System.out.print("lock");
            method();
        }
    }
    synchronized static void method() {
        System.out.print("method");
    };
}

synchronized (Sync.class) 先持有了 Sync 的类对象,然后再通过 synchronized 静态方法进行一次加锁,理论上说,反编译后应该是出现两对 monitorenter 和 monitorexit ,查看反编译 class 文件:

  public void function();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #8                  // class javatest/Sync
         2: dup
         3: astore_1
         4: monitorenter
         5: getstatic     #13                 // Field java/lang/System.out:Ljava/io/PrintStream;
         8: ldc           #19                 // String lock
        10: invokevirtual #20                 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
        13: invokestatic  #26                 // Method method:()V
        16: aload_1
        17: monitorexit
        18: goto          26
        21: astore_2
        22: aload_1
        23: monitorexit
        24: aload_2
        25: athrow
        26: return

method方法的字节码:

  static synchronized void method();
    descriptor: ()V
    flags: (0x0028) ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #13                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #29                 // String method
         5: invokevirtual #20                 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
         8: return

神奇的现象出现了,monitorenter 出现了一次, monitorexit 出现了两次,这和我们最开始只加一次锁的 demo 一致了。

那么是不是因为静态方法的原因呢,我们将 demo 改造成下面的效果:

public class Sync {
    public void function() {
        synchronized (Sync.class) {
            System.out.print("lock");
        }
        method();
    }
    void method() {
        synchronized (Sync.class) {
            System.out.print("method");
        }
    }
}

反编译结果:

  public void function();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #7                  // class javatest/Sync
         2: dup
         3: astore_1
         4: monitorenter
         5: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
         8: ldc           #15                 // String lock
        10: invokevirtual #17                 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
        13: aload_0
        14: invokevirtual #23                 // Method method:()V
        17: aload_1
        18: monitorexit
        19: goto          27
        22: astore_2
        23: aload_1
        24: monitorexit
        25: aload_2
        26: athrow
        27: return

method 方法的编译结果:

  void method();
    descriptor: ()V
    flags: (0x0000)
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #7                  // class javatest/Sync
         2: dup
         3: astore_1
         4: monitorenter
         5: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
         8: ldc           #26                 // String method
        10: invokevirtual #17                 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
        13: aload_1
        14: monitorexit
        15: goto          23
        18: astore_2
        19: aload_1
        20: monitorexit
        21: aload_2
        22: athrow
        23: return

从这里看,的确是出现了两组 monitorentermonitorexit 。

而从静态方法的 flags: (0x0028) ACC_STATIC, ACC_SYNCHRONIZED 中,我们可以看出,JVM 对于同步静态方法并不是通过monitorenter和 monitorexit 实现的,而是通过方法的 flags 中添加 ACC_SYNCHRONIZED 标记实现的。

而如果换一种方式,不使用嵌套加锁,改为连续执行两次对同一个对象加锁解锁:

public void function() {
    synchronized (Sync.class) {
        System.out.print("lock");
    }
    method();
}

反编译:

public void function();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #7                  // class javatest/Sync
         2: dup
         3: astore_1
         4: monitorenter
         5: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
         8: ldc           #15                 // String lock
        10: invokevirtual #17                 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
        13: aload_1
        14: monitorexit
        15: goto          23
        18: astore_2
        19: aload_1
        20: monitorexit
        21: aload_2
        22: athrow
        23: aload_0
        24: invokevirtual #23                 // Method method:()V
        27: return

method 方法的编译结果是:

  void method();
    descriptor: ()V
    flags: (0x0000)
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #7                  // class javatest/Sync
         2: dup
         3: astore_1
         4: monitorenter
         5: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
         8: ldc           #26                 // String method
        10: invokevirtual #17                 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
        13: aload_1
        14: monitorexit
        15: goto          23
        18: astore_2
        19: aload_1
        20: monitorexit
        21: aload_2
        22: athrow
        23: return

看来结果也是一样的,monitorenter 和 monitorexit 成对出现。

优点、缺点及优化

synchronized 关键字是 JVM 提供的 api ,是重量级锁,所以它具有重量级锁的优点,保持严格的互斥同步。

而缺点则同样是互斥同步的角度来说的:

  • 效率低:锁的释放情况少,只有代码执行完毕或者异常结束才会释放锁;试图获取锁的时候不能设定超时,不能中断一个正在使用锁的线程,相对而言,Lock 可以中断和设置超时。
  • 不够灵活:加锁和释放的时机单一,每个锁仅有一个单一的条件(某个对象)。

优化方案:Java 提供了java.util.concurrent 包,其中 Lock 相关的一些 API ,拓展了很多功能,可以考虑使用 J.U.C 中丰富的锁机制实现来替代 synchronized

其他说明

最后,本文环境基于:

java version "14.0.1" 2020-04-14
Java(TM) SE Runtime Environment (build 14.0.1+7)
Java HotSpot(TM) 64-Bit Server VM (build 14.0.1+7, mixed mode, sharing)
jdk version 1.8.0_312

到此这篇关于Java多线程并发synchronized 关键字的文章就介绍到这了,更多相关Java synchronized内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Java多线程并发synchronized 关键字

本文链接: https://www.lsjlt.com/news/151634.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Java多线程并发synchronized 关键字
    目录基础修饰普通方法修饰静态方法Synchronized 加锁原理monitorentermonitorexitsynchronized 修饰静态方法优点、缺点及优化其他说明基础 J...
    99+
    2024-04-02
  • Java多线程并发编程 Synchronized关键字
    synchronized 关键字解析同步锁依赖于对象,每个对象都有一个同步锁。现有一成员变量 Test,当线程 A 调用 Test 的 synchronized 方法,线程 A 获得 Test 的同步锁,同时,线程 B 也去调用 Test ...
    99+
    2023-05-31
    java synchronized 关键字
  • Java多线程之synchronized关键字的使用
    目录一、使用在非静态方法上二、使用在静态方法上三、使用在代码块上四、三种方式的区别4.1 不会互斥4.2 互斥一、使用在非静态方法上 public synchronized vo...
    99+
    2024-04-02
  • Java多线程synchronized关键字怎么输出
    这篇文章主要介绍“Java多线程synchronized关键字怎么输出”,在日常操作中,相信很多人在Java多线程synchronized关键字怎么输出问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java多...
    99+
    2023-06-17
  • Java多线程并发编程 Volatile关键字
    volatile 关键字是一个神秘的关键字,也许在 J2EE 上的 JAVA 程序员会了解多一点,但在 Android 上的 JAVA 程序员大多不了解这个关键字。只要稍了解不当就好容易导致一些并发上的错误发生,例如好多人把 volatil...
    99+
    2023-05-31
    java volatile 关键字
  • 怎样深入理解Java多线程与并发框中的synchronized 关键字
    怎样深入理解Java多线程与并发框中的synchronized 关键字,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。一、Class文件与对象对象头 32位JVM的对象头二、sy...
    99+
    2023-06-05
  • java线程安全Synchronized关键字怎么使用
    这篇文章主要介绍“java线程安全Synchronized关键字怎么使用”,在日常操作中,相信很多人在java线程安全Synchronized关键字怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”jav...
    99+
    2023-06-04
  • synchronized关键字 - [JAVA心得]
    Java对多线程的支持与同步机制深受大家的喜爱,似乎看起来使用了synchronized关键字就可以轻松地解决多线程共享数据同步问题。到底如何?――还得对synchronized关键字的作用进行深入了解才可定论。   总的说来,synchr...
    99+
    2023-06-03
  • Java中的synchronized关键字
    目录1、synchronized锁的底层实现原理2、基于synchronized实现单例模式3、利用类加载实现单例模式(饿汉模式)1、synchronized锁的底层实现原理 JV...
    99+
    2024-04-02
  • Java 关键字:synchronized详解
    synchronized详解 基本使用源码解析常见面试题好书推荐 基本使用 Java中的synchronized关键字用于在多线程环境下确保数据同步。它可以用来修饰方法和代码块 当一...
    99+
    2023-10-20
    java 开发语言 并发编程 JUC synchronized 原力计划
  • 如何深入理解Java多线程与并发框中的volatile关键字
    本篇文章为大家展示了如何深入理解Java多线程与并发框中的volatile关键字,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。概念把对 volatile变量的单个读/写,看成是使用 同一个监视器锁 ...
    99+
    2023-06-05
  • Java中的synchronized关键字怎么用
    小编给大家分享一下Java中的synchronized关键字怎么用,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!1、synchronized锁的底层实现原理JVM基于进入和退出Monitor对象来实现方法同步和代码块同步。...
    99+
    2023-06-25
  • 【JavaEE】Java中复杂的Synchronized关键字
    目录  一、synchronized的特性 (1)互斥 (2)刷新内存 (3)可重入 二、synchronized的使用 (1)修饰普通方法 (2)修饰静态方法 (3)修饰代码块 三、synchronized的锁机制 (1)基本特点 (2)...
    99+
    2023-09-04
    java java-ee 开发语言 jvm 面试
  • JAVA中native方法与synchronized 关键字
    native , synchronized [@more@]JAVA中native方法 @与羊共舞的狼 Java不是完美的,Java的不足除了体现在运行速度上要比传统的C++慢许多之外,Java无法直接访问到操作系统底层(如系统硬件...
    99+
    2023-06-03
  • 详解Java并发编程之volatile关键字
    目录1、volatile是什么?2、并发编程的三大特性3、什么是指令重排序?4、volatile有什么作用?5、volatile可以保证原子性?6、volatile 和 sy...
    99+
    2024-04-02
  • Java 多线程并发ReentrantLock
    目录背景ReentrantLock可重入特性公平锁设置参数源码分析Lock 接口加锁操作内部类SynctryLockinitialTryLocklocklockInterruptib...
    99+
    2024-04-02
  • Java多线程(二)——synchronized 详解
    目录 1 volatile 关键字 1.1保证变量可见性 1.2 不能保证数据的原子性举例 1.3 禁止JVM指令重排序 2 synchronized 关键字 2.1 概念及演进 2.2 对象锁和类锁 2.3 synchronized 的...
    99+
    2023-09-02
    java jvm 开发语言
  • Java 多线程并发LockSupport
    目录概览源码分析静态方法BlockerunparkUnsafe 的 unpark 方法park不带 blocker 参数的分组需要 blocker 参数的分组park/unpark ...
    99+
    2024-04-02
  • Java synchronized同步关键字工作原理
    目录一、简介二、synchronized的特性三、synchonized的使用及通过反汇编分析其原理修饰代码块monitorenter指令monitorexit指令修饰普通方法修饰静...
    99+
    2023-02-13
    Java synchronized Java synchronized原理
  • 如何在Java中使用synchronized关键字
    如何在Java中使用synchronized关键字?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。java基本数据类型有哪些Java的基本数据类型分为:1、整数类...
    99+
    2023-06-14
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作