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 的一个实现…

oracle中where中使用函数,Oracle 尽量避免在 SQL语句的WHERE子句中使用函数

-- Start在 WHERE 子句中应该尽量避免在列上使用函数,因为这样做会使该列上的索引失效,影响SQL 语句的性能。即使该列上没有索引,也应该避免在列上使用函数。考虑下面的情况:CREATE TABLE EMPLOYEE(NAME VARCHAR2(20) NOT NULL,--…

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

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

第三章(续)

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

Linux Mysql 安装方法

1、检查是否有安装 [rootJDDB mysql]# yum list installed | grep mysql mysql-community-client.x86_64 5.6.39-2.el7 mysql56-community mysql-community-common.x86_64 5.6.39-2.el7 mysql56-community mysql-community…

oracle 经纬度算距离,根据经纬度诀别用java和Oracle存储过程计算两点距离

根据经纬度分别用java和Oracle存储过程计算两点距离create or replace procedure SP_GET_DISTANCE(cx in number,cy in number,sx in number, sy in number,distance out varchar2)isd number;x number;y number;r number;pi number;begin--开始计算r:6371229;--地球半径pi:3.1…

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中的工厂模式

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

python的底层实现_Python底层封装实现方法详解

这篇文章主要介绍了Python底层封装实现方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下事实上,python封装特性的实现纯属“投机取巧”,之所以类对象无法直接调用私有方法和属性&a…

php 附近的距离,PHP查询附近的人及其距离的实现方法_PHP

本文实例讲述了PHP查询附近的人及其距离的实现方法。分享给大家供大家参考,具体如下:array(lat>$lat $dlat,lng>$lng-$dlng),right-top>array(lat>$lat $dlat, lng>$lng $dlng),left-bottom>array(lat>$lat - $dlat, lng>$ln…

统计指定目录下的视频时长

package time;import java.io.File;import org.apache.log4j.Logger;import it.sauronsoftware.jave.Encoder; import it.sauronsoftware.jave.EncoderException; import it.sauronsoftware.jave.MultimediaInfo;public class Test2 {/* 支持的后缀 */private static final Str…

怎么在cmd中运行python脚本_cmd中运行python脚本智能使用流程

(此时的ScaleMode自动变Vbuser)更有趣的是用来计算字串高、宽的TextHeight/TextWidth也变成以座标0-100的方式来表现了On Error Resume NextSet outstreemWscript.stdoutIf (LCase(Right(Wscript.fullname,11))"Wscript.exe") ThenSet objShellWscript.CreateObject(…

世界时钟 软件_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

oracle必须声明标识符函数,引用变量时需要必须声明标识符

SQL> declare2 pname emp.ename%type;3 psal emp.sal%type;4 begin5 select enmae,sal into pname,psal from emp where empno7782;6 dbms_output.put_line(pname||xsis||psal);7 end;8 /pname emp.ename%type;*第 2 行出现错误:ORA-06550: 第 2 行, 第 7 列:PLS-002…

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

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

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

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

hibernate oracle clob 注解,Hibernate3.X实现基于CLOB字段类型的注解方式:

一:Hibernate3.X实现基于CLOB字段类型的注解方式的例子:下面直接上代码:二:UserInfo.javapackage cn.gov.csrc.cms.model;import javax.persistence.Basic;import javax.persistence.Column;import javax.persistence.Entity;impo…

Flutter下拉刷新,上拉加载更多数据

下拉刷新 很简单,直接使用 RefreshIndicator 组件, onRefresh 为重新获取数据的方法 Widget build(BuildContext context) {return Scaffold(body: Container(padding: EdgeInsets.all(2.0),child: RefreshIndicator(onRefresh: _refresh,backgroundColo…