众所周知 synchronized 关键字是解决并发问题常用解决方案,有以下三种使用方式:

  • 同步普通方法,锁的是当前对象。
  • 同步静态方法,锁的是当前 Class 对象。
  • 同步块,锁的是 () 中的对象。

实现原理:

JVM 是通过进入、退出对象监视器( Monitor )来实现对方法、同步块的同步的。

具体实现是在编译之后在同步方法调用前加入一个 monitor.enter 指令,在退出方法和异常处插入 monitor.exit 的指令。

其本质就是对一个对象监视器( Monitor )进行获取,而这个获取过程具有排他性从而达到了同一时刻只能一个线程访问的目的。

而对于没有获取到锁的线程将会阻塞到方法入口处,直到获取锁的线程 monitor.exit 之后才能尝试继续获取锁。

流程图如下:

通过一段代码来演示:

    public static void main(String[] args) {
synchronized (Synchronize.class){
System.out.println("Synchronize");
}
}

使用 javap -c Synchronize 可以查看编译之后的具体信息。

public class com.crossoverjie.synchronize.Synchronize {
public com.crossoverjie.synchronize.Synchronize();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return public static void main(java.lang.String[]);
Code:
0: ldc #2 // class com/crossoverjie/synchronize/Synchronize
2: dup
3: astore_1
**4: monitorenter**
5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #4 // String Synchronize
10: invokevirtual #5 // Method java/io/PrintStream.println:(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
Exception table:
from to target type
5 15 18 any
18 21 18 any
}

可以看到在同步块的入口和出口分别有 monitorenter,monitorexit

指令。

锁优化

synchronized 很多都称之为重量锁,JDK1.6 中对 synchronized 进行了各种优化,为了能减少获取和释放锁带来的消耗引入了偏向锁轻量锁

轻量锁

当代码进入同步块时,如果同步对象为无锁状态时,当前线程会在栈帧中创建一个锁记录(Lock Record)区域,同时将锁对象的对象头中 Mark Word 拷贝到锁记录中,再尝试使用 CASMark Word 更新为指向锁记录的指针。

如果更新成功,当前线程就获得了锁。

如果更新失败 JVM 会先检查锁对象的 Mark Word 是否指向当前线程的锁记录。

如果是则说明当前线程拥有锁对象的锁,可以直接进入同步块。

不是则说明有其他线程抢占了锁,如果存在多个线程同时竞争一把锁,轻量锁就会膨胀为重量锁

解锁

轻量锁的解锁过程也是利用 CAS 来实现的,会尝试锁记录替换回锁对象的 Mark Word 。如果替换成功则说明整个同步操作完成,失败则说明有其他线程尝试获取锁,这时就会唤醒被挂起的线程(此时已经膨胀为重量锁)

轻量锁能提升性能的原因:

认为大多数锁在整个同步周期都不存在竞争,所以使用 CAS 比使用互斥开销更少。但如果锁竞争激烈,轻量锁就不但有互斥的开销,还有 CAS 的开销,甚至比重量锁更慢。

偏向锁

为了进一步的降低获取锁的代价,JDK1.6 之后还引入了偏向锁。

偏向锁的特征是:锁不存在多线程竞争,并且应由一个线程多次获得锁。

当线程访问同步块时,会使用 CAS 将线程 ID 更新到锁对象的 Mark Word 中,如果更新成功则获得偏向锁,并且之后每次进入这个对象锁相关的同步块时都不需要再次获取锁了。

释放锁

当有另外一个线程获取这个锁时,持有偏向锁的线程就会释放锁,释放时会等待全局安全点(这一时刻没有字节码运行),接着会暂停拥有偏向锁的线程,根据锁对象目前是否被锁来判定将对象头中的 Mark Word 设置为无锁或者是轻量锁状态。

偏向锁可以提高带有同步却没有竞争的程序性能,但如果程序中大多数锁都存在竞争时,那偏向锁就起不到太大作用。可以使用 -XX:-userBiasedLocking=false 来关闭偏向锁,并默认进入轻量锁。

其他优化

适应性自旋

在使用 CAS 时,如果操作失败,CAS 会自旋再次尝试。由于自旋是需要消耗 CPU 资源的,所以如果长期自旋就白白浪费了 CPUJDK1.6加入了适应性自旋:

如果某个锁自旋很少成功获得,那么下一次就会减少自旋。

总结

synchronized 现在已经不像以前那么重了,拿 1.8 中的 ConcurrentHashMap 就可以看出,里面大量的使用了 synchronized 来进行同步。

号外

最近在总结一些 Java 相关的知识点,感兴趣的朋友可以一起维护。

地址: https://github.com/crossoverJie/Java-Interview

synchronize 关键字原理的更多相关文章

  1. Java 多线程 - synchronize 关键字

    目录 Java 多线程 - synchronize 关键字 Java 多线程 - synchronize 关键字 学习自 http://cmsblogs.com/?p=2071 https://www ...

  2. Java内存模型以及Volatile、Synchronize关键字的疑问

    1.众所周知,java的内存模型是一个主内存,每个线程都有一个工作内存空间,那么主内存同步到工作内存是什么时候发生的呢?工作内存同步会主内存又是什么时候发生的呢? 在cpu进行线程切换时就会发生这些同 ...

  3. c# yield关键字原理

    https://www.cnblogs.com/blueberryzzz/p/8678700.html c# yield关键字原理详解 1.yield实现的功能yield return:先看下面的代码 ...

  4. Java精通并发-synchronized关键字原理详解

    关于synchronized关键字原理其实在当时JVM的学习[https://www.cnblogs.com/webor2006/p/9595300.html]中已经剖析过了,这里从研究并发专题的角度 ...

  5. c# yield关键字原理详解

    c# yield关键字的用法 1.yield实现的功能 yield return: 先看下面的代码,通过yield return实现了类似用foreach遍历数组的功能,说明yield return也 ...

  6. java synchronized 关键字原理

    Synchronized 关键字是解决并发问题常用解决方案,有以下三种使用方式: 同步普通方法,锁的是当前对象.同步静态方法,锁的是当前 Class 对象.同步块,锁的是 {} 中的对象. 实现原理: ...

  7. java:volatile关键字原理

    volatile说明 在变量中声明后,能够在所有线程中共享改变量.并且volatile关键字能防止指令重排,即程序读取到volatile时,则不会将程序执行顺序修改. 先了解下内存模型 cpu内存模型 ...

  8. java多线程基础(synchronize关键字)

    [toc] 基础知识 ---- 线程:进程(process)就是一块包含了某些资源的内存区域.操作系统利用进程把它的工作划分为一些功能单元. 线程:进程中所包含的一个或多个执行单元称为线程(threa ...

  9. Java 中冷门的 synthetic 关键字原理解读

    看JAVA的反射时,看到有个synthetic ,还有一个方法isSynthetic() 很好奇,就了解了一下: 1.定义 Any constructs introduced by a Java co ...

随机推荐

  1. UOJ#196. 【ZJOI2016】线段树 概率期望,动态规划

    原文链接www.cnblogs.com/zhouzhendong/p/UOJ196.html 题解 先离散化,设离散化后的值域为 $[0,m]$ . 首先把问题转化一下,变成:对于每一个位置 $i$ ...

  2. easyUI 创建有复选框的table.datagrid

    table : function(data){ pt.v.table.datagrid({ // singleSelect:true, height:295, columns:[[ {field:'x ...

  3. pytroch nn.Module源码解析(1)

    今天在写一个分类网络时,要使用nn.Sequential中的一个模块,因为nn.Sequential中模块都没有名字,我一时竟无从下笔.于是决定写这篇博客梳理pytorch的nn.Module类,看完 ...

  4. vbs脚本实现自动打字祝福&搞笑

    脚本祝福礼物 概述 听说抖音上流行一种用代码做程序表白的东西,,,, 当然我也不是要表白,,,, 但是好像蛮有意思的,,,, 于是,又学了一下vbs脚本,做了几个很不错的祝福脚本,不懂代码的可以直接戳 ...

  5. vue 图片预览插件

    https://github.com/daidaitu1314/vue2-preview //cnpm cnpm install vue2-preview -save //引入 import VueP ...

  6. IIS7配置伪静态把后缀名映射为html

    1.在IIS新建站点.[ 创建的时候不用去选择版本和模式,默认即可 ] 2.选中站点,切换到功能试图,找到“处理程序映射",双击之后,在打开窗口右侧的操作栏目下做如下设置: 1) 右边&qu ...

  7. vue 源码学习二 实例初始化和挂载过程

    vue 入口 从vue的构建过程可以知道,web环境下,入口文件在 src/platforms/web/entry-runtime-with-compiler.js(以Runtime + Compil ...

  8. CentOS 7.3/Linux .net core sdk 安装

    执行下列命令,安装.NET Core SDK(微软官方教程地址 https://www.microsoft.com/net/learn/get-started/linuxcentos) 点开链接,选择 ...

  9. win10 vscode使用 智能提示

    1.没有第三方库的智能提示 参考:https://code.visualstudio.com/docs/python/editing 1.点开Settings 2.搜索加添加 3.添加后的内容 然后就 ...

  10. 文件上传控件bootstrap-fileinput的使用

    1.插件下载地址:https://github.com/kartik-v/bootstrap-fileinput 2.插件的引用 需要引用jquery 需要结合bootstrap使用,即页面需要引入b ...