synchronized 方法 导致插入数据插不进_synchronized 原理知多少

本文转载于SegmentFault社区

作者:ytao


synchronized是 Java 编程中的一个重要的关键字,也是多线程编程中不可或缺的一员。本文就对它的使用和锁的一些重要概念进行分析。

使用及原理


synchronized 是一个重量级锁,它主要实现同步操作,在 Java 对象锁中有三种使用方式:

  • 普通方法中使用,锁是当前实例对象。

  • 静态方法中使用,锁是当前类的对象。

  • 代码块中使用,锁是代码代码块中配置的对象。

使用


在代码中使用方法分别如下:

普通方法使用:

/** * 公众号:ytao * 博客:https://ytao.top */public class SynchronizedMethodDemo{    public synchronized void demo(){        // ......    }}

静态方法使用:

/** * 公众号:ytao * 博客:https://ytao.top */public class SynchronizedMethodDemo{    public synchronized static void staticDemo(){        // ......    }}

代码块中使用:

/** * 公众号:ytao * 博客:https://ytao.top */public class SynchronizedDemo{    public void demo(){        synchronized (SynchronizedDemo.class){            // ......        }    }}

实现原理


方法和代码块的实现原理使用不同方式:

代码块

每个对象都拥有一个monitor对象,代码块的{}中会插入monitorenter和monitorexit指令。当执行monitorenter指令时,会进入monitor对象获取锁,当执行monitorexit命令时,会退出monitor对象释放锁。同一时刻,只能有一个线程进入在monitorenter中。

先将SynchronizedDemo.java使用javac SynchronizedDemo.java命令将其编译成SynchronizedDemo.class。然后使用javap -c SynchronizedDemo.class反编译字节码。Compiled from "SynchronizedDemo.java"public class SynchronizedDemo {  public SynchronizedDemo();    Code:       0: aload_0       1: invokespecial #1                  // Method java/lang/Object."":()V       4: return  public void demo();    Code:       0: ldc           #2                  // class SynchronizedDemo       2: dup       3: astore_1       4: monitorenter  // 进入 monitor       5: aload_1       6: monitorexit  // 退出 monitor       7: goto          15      10: astore_2      11: aload_1      12: monitorexit  // 退出 monitor      13: aload_2      14: athrow      15: return    Exception table:       from    to  target type           5     7    10   any          10    13    10   any}

上面反编码后的代码,有两个monitorexit指令,一个插入在异常位置,一个插入在方法结束位置。

方法


方法中的synchronized与代码块中实现的方式不同,方法中会添加一个叫ACC_SYNCHRONIZED的标志,当调用方法时,首先会检查是否有ACC_SYNCHRONIZED标志,如果存在,则获取monitor对象,调用monitorenter和monitorexit指令。

通过javap -v -c SynchronizedMethodDemo.class命令反编译SynchronizedMethodDemo类。-v参数即-verbose,表示输出反编译的附加信息。下面以反编译普通方法为例。

Classfile /E:/SynchronizedMethodDemo.class  Last modified 2020-6-28; size 381 bytes  MD5 checksum 55ca2bbd9b6939bbd515c3ad9e59d10c  Compiled from "SynchronizedMethodDemo.java"public class SynchronizedMethodDemo  minor version: 0  major version: 52  flags: ACC_PUBLIC, ACC_SUPERConstant pool:   #1 = Methodref          #5.#13         // java/lang/Object."":()V   #2 = Fieldref           #14.#15        // java/lang/System.out:Ljava/io/PrintStream;   #3 = Methodref          #16.#17        // java/io/PrintStream.println:()V   #4 = Class              #18            // SynchronizedMethodDemo   #5 = Class              #19            // java/lang/Object   #6 = Utf8                  #7 = Utf8               ()V   #8 = Utf8               Code   #9 = Utf8               LineNumberTable  #10 = Utf8               demo  #11 = Utf8               SourceFile  #12 = Utf8               SynchronizedMethodDemo.java  #13 = NameAndType        #6:#7          // "":()V  #14 = Class              #20            // java/lang/System  #15 = NameAndType        #21:#22        // out:Ljava/io/PrintStream;  #16 = Class              #23            // java/io/PrintStream  #17 = NameAndType        #24:#7         // println:()V  #18 = Utf8               SynchronizedMethodDemo  #19 = Utf8               java/lang/Object  #20 = Utf8               java/lang/System  #21 = Utf8               out  #22 = Utf8               Ljava/io/PrintStream;  #23 = Utf8               java/io/PrintStream  #24 = Utf8               println{  public SynchronizedMethodDemo();    descriptor: ()V    flags: ACC_PUBLIC    Code:      stack=1, locals=1, args_size=1         0: aload_0         1: invokespecial #1                  // Method java/lang/Object."":()V         4: return      LineNumberTable:        line 5: 0  public synchronized void demo();    descriptor: ()V    flags: ACC_PUBLIC, ACC_SYNCHRONIZED     // ACC_SYNCHRONIZED 标志    Code:      stack=1, locals=1, args_size=1         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;         3: invokevirtual #3                  // Method java/io/PrintStream.println:()V         6: return      LineNumberTable:        line 8: 0        line 10: 6}SourceFile: "SynchronizedMethodDemo.java"

上面对代码块和方法的实现方式进行探究:

  • 代码块通过在编译后的代码中添加monitorenter和monitorexit指令。

  • 方法中通过添加ACC_SYNCHRONIZED标志,来决定是否调用monitor对象。

Java 对象头


synchronized锁的相关数据存放在 Java 对象头中。Java 对象头指的 HotSpot 虚拟机的对象头,使用2个字宽或3个字宽存储对象头。

  • 第一部分存储运行时的数据,hashCode、锁标记位、是否偏向锁、GC分代年龄等等信息,称作为Mark Word。

  • 第二部分存储对象类型数据的指针。

  • 第三部分,如果对象是数组的话,则用这部分来存储数组长度。

Java 对象头 Mark Word 存储内容:

7d746c51b907f9d518e4b87621e90c52.png

锁升级


synchronized 称为重量级锁,但 Java SE 1.6 为优化该锁的性能而减少获取和释放锁的性能消耗,引入偏向锁和轻量级锁。

锁的高低级别为:无锁→偏向锁→轻量级锁→重量级锁。

其中锁的升级是不可逆的,只能由低往高级别升,不能由高往低降。

偏向锁


偏向锁是优化在无多线程竞争情况下,提高程序的的运行性能而使用到的锁。在Mark Word中存储一个值,用来标志是否为偏向锁,在 32 位虚拟机和 64 位虚拟机中都是使用一个字节存储,0 为非偏向锁,1 为是偏向锁。

当第一次被线程获取偏向锁时,会将Mark Word中的偏向锁标志设置为 1,同时使用 CAS 操作来记录这个线程的ID。获取到偏向锁的线程,再次进入获取锁时,只需判断Mark Word是否存储着当前线程ID,如果是,则不需再次进行获取锁操作,而是直接持有该锁。

撤销锁

如果有其他线程出现,尝试获取偏向锁,让偏向锁处于竞争状态,那么当前偏向锁就会撤销。

撤销偏向锁时,首先会暂停持有偏向锁的线程,并将线程ID设为空,然后检查该线程是否存活:

  • 当暂停线程非存活,则设置对象头为无锁状态。

  • 当暂停线程存活,执行偏向锁的栈,最后对象头的保存其他获取到偏向锁的线程ID或者转向无锁状态。

当确定代码一定执行在多线程访问中时,那么这时的偏向锁是无法发挥到优势,如果继续使用偏向锁就显得过于累赘,给系统带来不必要的性能开销,此时可以设置 JVM 参数-XX:BiasedLocking=false来关闭偏向锁。

轻量级锁


代码进入同步块的时候,如果对象头不是锁定状态,JVM 则会在当前线程的栈桢中创建一个锁记录的空间,将锁对象头的Mark Word复制一份到锁记录中,这份复制过来的Mark Word叫做Displaced Mark Word。然后使用 CAS 操作将锁对象头中的Mark Word更新为指向锁记录的指针。如果更新成功,当前线程则会获得锁,如果失败,JVM 先检查锁对象的Mark Word是否指向当前线程,是指向当前线程的话,则当前线程已持有锁,否则存在多线程竞争,当前线程会通过自旋获取锁,这里的自旋可以理解为循环尝试获取锁,所以这过程是消耗 CPU 的过程。当轻量级锁存在竞争状态并自旋获取轻量级锁失败时,轻量级锁就会膨胀为重量级锁,锁对象的Mark Word会更新为指向重量级锁的指针,等待获取锁的线程进入阻塞状态。

解锁

轻量级锁解锁是使用 CAS 操作将锁记录替换到Mark Word中,如果替换成功,则表示同步操作已完成。如果失败,则表示其他竞争线程尝试过获取该轻量级锁,需要在释放锁的同时,去唤醒其他被阻塞的线程,被唤醒的线程回去再次去竞争锁。

总结


通过分析synchronized的使用以及 Java SE 1.6 升级优化锁后的设计,可以看出其主要是解决是通过多加入两级相对更轻巧的偏向锁和轻量级锁来优化重量级锁的性能消耗,但是这并不是一定会起到优化作用,主要是解决大多数情况下不存在多线程竞争以及同一线程多次获取锁的的优化,这也是根据平时在编码中多观察多反思得出的权衡方案。

推荐阅读:

《volatile 手摸手带你解析》:
https://ytao.top/2020/03/15/18-volatile/

《Java 线程通信之 wait/notify 机制》:

https://ytao.top/2020/05/12/24-thread-wait-notify/

《Java 多线程中使用 JDK 自带工具类实现计数器》:

https://ytao.top/2020/05/17/25-thread-count/

《Java 线程基础,从这篇开始》:

https://ytao.top/2020/04/19/22-thread-base/

- END -

c6111bcddfe414345c0c5aaf2f5ebc22.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/538325.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringMVC源码解析(四)——请求处理

2019独角兽企业重金招聘Python工程师标准>>> 前言 这一篇,将着手介绍一次请求的处理。用到了 HandlerMapping、HandlerAdapter 知识,如果遇到不是太了解,可以回顾下。 源码分析 其实 DispatcherServlet 也只是 Servlet 的一个实现…

求近似数最值_干货|初中数学《数的开方》知识点梳理

本章内容课标的要求● 1.了解平方根、算术平方根、立方根的概念,会用根号表示数的平方根、算术平方根、立方根。● 2.了解乘方与开方互为逆运算,会用平方运算求百以内整数的平方根,会用立方运算会求百以内整数(对应的负整数)的立方根&#xf…

第三章(续)

目录 第二章 灰度变换与空间滤波(续)直方图处理与函数绘图生成直方图直方图均衡直方图匹配空间滤波线性空间滤波非线性空间滤波图像处理工具箱的标准滤波器线性空间滤波器非线性空间滤波器第二章 灰度变换与空间滤波(续) 直方图处理与函数绘图 生成直方图 应用函数 imhist 语法…

Kafka集群安装--测试--关闭

一、前提 1、kafka安装包下载:http://kafka.apache.org/downloads 2、jdk已安装 3、scala已安装 4、zookeeper集群已安装并运行二、步骤 1、对kafka_2.9.2-0.8.1.tgz进行解压缩:tar -zxvf kafka_2.9.2-0.8.1.tgz。2、对kafka目录进行改名:mv …

Java中的工厂模式

设计模式遵循原则 开闭原则:对扩展开放,对修改关闭里氏代换原则:只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被覆用。而衍生类也能够在基类的基础上增加新的行为依赖倒转原则:开闭…

世界时钟 软件_Clocker for Mac(世界时钟软件)

Clocker for Mac是一款Mac平台上免费的世界时钟工具,方便我们查看世界各地的时间,它是开源免费的,完全没有广告。包括数百个时区,支持24小时制或AM / PM,macz提供Clocker mac免费版,欢迎前来下载&#xff0…

Mac 设置 NDK

2019独角兽企业重金招聘Python工程师标准>>> 1、首先查看我自己的android studio ,找到以下路径 如上图,打开一个 AS 项目,file - project structure 这是我的3 个路径 Ndk /Users/dhbm/Library/Android/sdk/ndk-bundle Sdk /User…

Workbench has not been created yet

原因是:加载的插件变更后需要清理 在启动参数最后加入 -clean

四参数拟合曲线_每周放送|曲线拟合

曲线拟合No.1什么是曲线拟合所谓的曲线拟合,就是使用某一个模型(或者称为方程式),将一系列的数据拟成平滑的曲线,以便观察两组数据之间的内在联系,了解数据之间的变化趋势。No.2曲线拟合的应用在数据分析时,我们有时需…

Spark集群运行jar包程序里的print日志哪里去了?

默认情况下,是输出到stdout里的。 方法一: 进入work所在机器的spark安装目录下的work目录,里面有日志输出。 方法二: 进入spark web ui 里 点击stdout就可以查看,如果没有可能在其他work上。

qt 批量裁剪图片_照片变素描,不用下载App,好用的在线图片处理及图库

我们要处理图片时,无论是在电脑还是手机上,往往都需要下载软件,但如果你只是临时用一下的话,下载软件难免显得工程有点浩大。下面就推荐几个图片处理网站,打开网页就能用。1、图片处理 funny。pho。to这个网站提供了很…

Intellij IDEA15:建立Scala的Maven项目

原文链接:http://blog.csdn.net/silentwolfyh/article/details/51172369 ------------------------------------------------------------------------------------ 在创建Scala的Maven之前要安装JavaJDK 、 Scala 的JDK 、 Maven配置,请参考下面 Int…

linux临时启动进程命令,Linux常用命令(一)服务控制及优化启动过程

Linux常用命令(一)服务控制及优化启动过程一、Red hat系统开机引导过程1、linux操作系统的引导过程一般包括以下几个阶段:开机自检、MBR引导、GRUB菜单、加载Linux内核、init进程初始化。2、Linux系统中的进程使用数字进行标记,每个进程的身份标记号称为…

linux 解决端口占用

2019独角兽企业重金招聘Python工程师标准>>> 1. netstat -apn|grep 9876 2.kill -9 端口号 --------- java 在linux后台运行的命令 ------------- nohup java -jar demo-shiro-0.0.1-SNAPSHOT.jar >temp.txt & 转载于:https://my.oschina.net/likaixuan0/…

IDEA中 @override报错的解决方法

原文路径:http://www.cnblogs.com/printN/p/6870036.html ------------------------------------ 今天用IDEA导入一个java工程时,碰上一个问题,代码中所有override处标红,并提示:override不支持对接口的实现。 网上百…

Linux目录的可写意味着,Linux权限分析 - osc_h5427nyq的个人空间 - OSCHINA - 中文开源技术交流社区...

在学习Linux的权限之前,我们先来理解几个概念:可读,可写 、可执行Linux的文件和目录有以下三种方式:r 、w 、x:可读,可写 、可执行r-可读(read)w-可写(write)x-可执行(execute)所有者 、所属组 、其他人Linux的文件和目录又可以有…

【深度学习篇】--Windows 64下tensorflow-gpu安装到应用

一、前述 一直以为自己的笔记本不支持tensflow-gpu的运行,结果每次运行模型都要好久。偶然间一个想法,想试试自己的笔记本,结果竟然神奇的发现能用GPU。于是分享一下安装步骤. 二、具体 因为版本之间有严格的对应关系,所以本文就将…

idea中Error:java: Compilation failed: internal java compiler error

Error:java: Compilation failed: internal java compiler error 原因是没有设置好 java compiler,检查下 File-----setting---compiler----java compoler

linux版本fedora,技术|初级:如何更新 Fedora Linux 系统

本快速教程介绍了更新 Fedora Linux 安装的多种方法。安装 Fedora 之后,我做的第一件事就是尝试安装一些软件。我打开软件中心,发现该软件中心已“损坏”。 我无法从中安装任何应用程序。我不确定我的系统出了什么问题。在团队内部讨论时,Abh…

linux 开源邮件 系统,4 个开源的命令行邮件客户端

无论你承认与否,email并没有消亡。对那些对命令行至死不渝的 Linux 高级用户而言,离开 shell 转而使用传统的桌面或网页版邮件客户端并不适应。归根结底,命令行最善于处理文件,特别是文本文件,能使效率倍增。幸运的是&…