Synchronize关键字 - 原理

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

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

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

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

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

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

流程图如下:

通过一段代码来演示:

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

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

  1. public class com.crossoverjie.synchronize.Synchronize {
  2.  public com.crossoverjie.synchronize.Synchronize();
  3.    Code:
  4.       0: aload_0
  5.       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
  6.       4: return
  7.  public static void main(java.lang.String[]);
  8.    Code:
  9.       0: ldc           #2                  // class com/crossoverjie/synchronize/Synchronize
  10.       2: dup
  11.       3: astore_1
  12.       **4: monitorenter**
  13.       5: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
  14.       8: ldc           #4                  // String Synchronize
  15.      10: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  16.      13: aload_1
  17.      **14: monitorexit**
  18.      15: goto          23
  19.      18: astore_2
  20.      19: aload_1
  21.      20: monitorexit
  22.      21: aload_2
  23.      22: athrow
  24.      23: return
  25.    Exception table:
  26.       from    to  target type
  27.           5    15    18   any
  28.          18    21    18   any
  29. }

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

锁优化

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

轻量锁

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

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

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

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

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

解锁

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

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

偏向锁

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

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

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

释放锁

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

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

其他优化

适应性自旋

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

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

原文地址:https://www.cnblogs.com/charm-j/p/10307608.html

时间: 2024-11-08 17:56:23

Synchronize关键字 - 原理的相关文章

JAVA多线程之Synchronize 关键字原理

image 众所周知 Synchronize 关键字是解决并发问题常用解决方案,有以下三种使用方式: 同步普通方法,锁的是当前对象. 同步静态方法,锁的是当前 Class 对象. 同步块,锁的是 {} 中的对象. 实现原理: JVM 是通过进入.退出对象监视器( Monitor )来实现对方法.同步块的同步的. 具体实现是在编译之后在同步方法调用前加入一个 monitor.enter 指令,在退出方法和异常处插入 monitor.exit 的指令. 其本质就是对一个对象监视器( Monitor

synchronize底层原理

synchronize底层原理是什么?我们先通过反编译下面的代码来看看Synchronized是如何实现对代码块进行同步的: 1 package com.paddx.test.concurrent; 2 3 public class SynchronizedDemo { 4 public void method() { 5 synchronized (this) { 6 System.out.println("Method 1 start"); 7 } 8 } 9 } 反编译结果: 关

c# yield关键字原理详解

c# yield关键字的用法 1.yield实现的功能 yield return: 先看下面的代码,通过yield return实现了类似用foreach遍历数组的功能,说明yield return也是用来实现迭代器的功能的. using static System.Console; using System.Collections.Generic; class Program { //一个返回类型为IEnumerable<int>,其中包含三个yield return public stat

Java多线程之深入理解synchronize关键字

synchronize锁重入: 关键字synchronize拥有锁重入的功能,也就是在使用synchronize时,当一个线程的得到了一个对象的锁后,再次请求此对象是可以再次得到该对象的锁. 当一个线程请求一个由其他线程持有的锁时,发出请求的线程就会被阻塞,然而,由于内置锁是可重入的,因此如果某个线程试图获得一个已经由她自己持有的锁,那么这个请求就会成功,"重入" 意味着获取锁的 操作的粒度是"线程",而不是调用. public class SyncDubbol {

对象的创建与内存分配

创建对象 当 JVM 收到一个 new 指令时,会检查指令中的参数在常量池是否有这个符号的引用,还会检查该类是否已经被加载过了,如果没有的话则要进行一次类加载. 接着就是分配内存了,通常有两种方式: 指针碰撞 空闲列表 使用指针碰撞的前提是堆内存是完全工整的,用过的内存和没用的内存各在一边每次分配的时候只需要将指针向空闲内存一方移动一段和内存大小相等区域即可. 当堆中已经使用的内存和未使用的内存互相交错时,指针碰撞的方式就行不通了,这时就需要采用空闲列表的方式.虚拟机会维护一个空闲的列表,用于记

java线程总结--synchronized关键字,原理以及相关的锁

在多线程编程中,synchronized关键字非常常见,当我们需要进行"同步"操作时,我们很多时候需要该该关键字对代码块或者方法进行锁定.被synchronized锁定的代码块,只能同时有一条线程访问该代码块. 上面是很多人的认识,当然也是我之前对synchronized关键字的浅显认识,其实上面的观点存在一定的偏差.在参考了很多文章以及自己动手测试过相关代码后,我觉得有必要记录下自己对synchronized关键字的一些理解,在这个过程,会简单说说synchronized关键字的具体

夯实基础之--new关键字、instanceOf原理

1.instanceOf原理  检测右边构造函数的prototype是否在左边对象的原型链上,在返回true,不在返回false 例:function  Persion(name,age){ this.name = name; this.age  = age; } ; let  p  = new  Persion() console.log( p   instanceOf  Persion )      //true 对象p的原型链为:p.__proto__  ==   Persion.prot

Java中Atomic包的实现原理及应用

同步问题的提出         假设我们使用一个双核处理器执行A和B两个线程,核1执行A线程,而核2执行B线程,这两个线程现在都要对名为obj的对象的成员变量i进行加1操作,假设i的初始值为0,理论上两个线程运行后i的值应该变成2,但实际上很有可能结果为1.          我们现在来分析原因,这里为了分析的简单,我们不考虑缓存的情况,实际上有缓存会使结果为1的可能性增大.A线程将内存中的变量i读取到核1算数运算单元中,然后进行加1操作,再将这个计算结果写回到内存中,因为上述操作不是原子操作,

Java并发编程 Synchronized及其实现原理

Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法.Synchronized的作用主要有三个:(1)确保线程互斥的访问同步代码(2)保证共享变量的修改能够及时可见(3)有效解决重排序问题. Java中每一个对象都可以作为锁,这是synchronized实现同步的基础: 1.普通同步方法,锁是当前实例对象 public class SynchronizedTest { 4 public synchronized void method1(){ 5 System