Synchronized原理

Posted 无虑的小猪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Synchronized原理相关的知识,希望对你有一定的参考价值。

1、Synchronized是什么

  Synchronized是Java中的关键字。

2、Synchronized的作用

  Synchronized可避免多线程同时操作临界资源,同一时间点,只会有一个线程操作临界资源,保证了操作的原子性。

3、Synchronized的使用

  Synchronized可以修饰静态方法、非静态方法、代码块。Synchronized示例详情:

 import java.util.concurrent.TimeUnit;
 
 public class TestSynchronized 
 
     private static int count;
 
     public static void main(String[] args) throws Exception 
         int finalIndex = 20;
         TestSynchronized testSynchronized = new TestSynchronized();
         for (int i = 0; i < finalIndex; i++) 
             new Thread(() -> 
                 testSynchronized.add("没有synchronized修饰的添加方法");
             ).start();
         
         TimeUnit.SECONDS.sleep(2);
         System.out.println("============================================================");
         count = 0;
         for (int i = 0; i < finalIndex; i++) 
             new Thread(() -> 
                 add01("synchronized修饰静态方法");
             ).start();
         
         TimeUnit.SECONDS.sleep(2);
         System.out.println("============================================================");
         count = 0;
         for (int i = 0; i < finalIndex; i++) 
             new Thread(() -> 
                 testSynchronized.add02("synchronized修饰非静态方法");
             ).start();
         
         TimeUnit.SECONDS.sleep(2);
         System.out.println("============================================================");
         count = 0;
         for (int i = 0; i < finalIndex; i++) 
             new Thread(() -> 
                 testSynchronized.add03("synchronized修饰代码块");
             ).start();
         
     
 
     public void add(String msg) 
         count++;
         System.out.println(msg + " :" + count);
     
 
     public static  synchronized void add01(String msg) 
         count ++;
         System.out.println(msg + " :" + count);
     
 
     public synchronized void add02(String msg) 
         count ++;
         System.out.println(msg + " :" + count);
     
 
     public void add03(String msg) 
         synchronized (TestSynchronized.class) 
             count ++;
             System.out.println(msg + " :" + count);
         
     
 

  运行后会发现,add方法的输出结果出现问题。add01、add02、add03方法的输出结果正常。

  首先count++是非原子的操作,在JVM字节码中的执行过程如下:

  

  下面来看看,Synchronized是如何保证原子性的。Synchronized修饰方法,会在对应方法上打上同步标识,ACC_SYNCHRONIZED:

  

 

  

   Synchronized修饰代码块,只有获得锁资源的线程可执行monitorenter指令后面的流程;在代码块执行结束或抛出异常时,执行monitorexit指令释放锁资源,以便其他线程争抢锁资源,

  monitorenter、monitorexit指令保证了同一时间点,只会有一个线程执行同步代码块中的内容。

  

4、Synchronized的优化

  Synchronized在JDK1.5做了锁消除、锁膨胀的优化,在JDK1.6做了锁升级的优化。

4.1、锁消除

  在synchronized修饰的代码块中,若不存在操作临界资源的情况,会触发锁消除,即便有synchronized修饰,也不会触发。

4.2、锁膨胀

  频繁的获取和释放资源,会将锁的范围扩大,减少性能的消耗。

4.3、锁升级

  Synchronized锁升级是在JDK1.6做的优化,在这之前Synchronized修饰的代码块,若线程未获取到锁,挂起当前线程。在JDK1.6做了锁升级的优化,无锁、偏向锁、轻量级锁、重量级锁。

5、Synchronized的实现原理

  Synchronized根据类锁或对象锁实现的。Synchronized修饰static静态方法,使用的是类锁,当前对象的class;Synchronized修饰非static静态方法,使用的是对象锁,当前对象this作为锁。synchronized是基于对象实现的,在JVM中,堆中的对象由对象头、实例数据、对齐填充这三部分构成。

  MarkWord中标记着四种锁的信息:无锁、偏向锁、轻量级锁、重量级锁。锁信息记录在对象的对象头Mard Word中,详情如下图所示:

锁状态说明:

1、无锁

  没有线程操作临界资源。

2、偏向锁

  若当前资源,仅有一个线程在获取和释放锁资源,当该线程操作临界资源,只需判断指向的线程是否为当前线程。若为当前线程,成功获取锁资源;若不是当前线程,那就是出现了锁竞争,通过CAS的方式尝试将  锁指向当前线程,若获取不到锁资源,触发锁升级,升级成轻量级锁。

3、轻量级锁

  自旋锁的方式通过CAS的方式获取锁资源,若CAS成功,获取到锁资源;若CAS一直失败,自旋到一定的次数,还没有获取到锁资源,进行锁升级,升级重量级锁。

4、重量级锁

  拿不到锁资源,挂起当前线程。涉及用户态、内核态的切换,耗性能。

 

synchronized 实现原理

文章目录

synchronized作用在代码块

synchronized作用在代码块时,它的底层是通过monitorenter、monitorexit指令来实现的。

注意一点:synchronized可以修饰静态方法,但不能修饰静态代码块。
当修饰静态方法时,监视器锁(monitor)便是对象的Class实例,因为Class数据存在于永久代,因此静态方法锁相当于该类的一个全局锁。
静态代码块是类初始化时候运行的一段代码,无法初始化出Class实例

monitorenter:

每个对象都是一个监视器锁(monitor),当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权

这也是Synchronized 为什么被称为重量级锁的原因:

  • Synchronized 是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本质又是依赖于底层的操作系统的 Mutex Lock(互斥锁)来实现的。而操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么 Synchronized 效率低的原因。因此,这种依赖于操作系统 Mutex Lock 所实现的锁我们称之为 “重量级锁”。

指令执行过程如下:

  • 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1。
  • 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

monitorexit:

注意:执行monitorexit的线程必须是objectref所对应的monitor持有者。

指令执行过程如下:

  • monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个monitor的所有权。

monitorexit指令出现了两次,第1次为同步正常退出释放锁,第2次为发生异步退出释放锁。

synchronized作用在方法上

方法的同步并没有通过 monitorenter 和 monitorexit 指令来完成,不过相对于代码块,其常量池中多了 ACC_SYNCHRONIZED 标示符。

JVM就是根据该标示符来实现方法的同步的,执行步骤如下:

  • 当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。
  • 在方法执行期间,其他任何线程都无法再获得同一个monitor对象。

synchronized锁存储位置

Synchronized用的锁是存在java的对象头里面的。一个对象在new出来之后再内存中主要分为4个部分:

模块说明
Mark Word存储了对象的hashCode、GC信息、锁信息三部分。这部分占8字节。
Class Pointer存储了指向类对象信息的指针。在64位JVM上有一个压缩指针选项-ClassPointer指针:-XX:+UseCompressedClassPointers 为4字节 不开启为8字节。默认是开启的。
实例数据(instance data)记录了对象里面的变量数据。引用类型:-XX:+UseCompressedOops 为4字节 不开启为8字节 Oops Ordinary Object Pointers
Padding作为对齐使用,对象在64位服务版本中,规定对象内存必须要能被8字节整除,如果不能整除,那么久靠对齐来不。举个例子:new出了一个对象,内存只占用18字节,但是规定要能被8整除,所以padding=6

Mark Word存储结构如下:

32位虚拟机下:

64位虚拟机下:

下面我们以 32位虚拟机为例,来看一下其 Mark Word 的字节具体是如何分配的:

  • 无锁 :对象头开辟 25bit 的空间用来存储对象的 hashcode ,4bit 用于存放对象分代年龄,1bit
    用来存放是否偏向锁的标识位,2bit 用来存放锁标识位为01
  • 偏向锁: 在偏向锁中划分更细,还是开辟 25bit 的空间,其中23bit 用来存放线程ID,2bit 用来存
    放 Epoch,4bit 存放对象分代年龄,1bit 存放是否偏向锁标识, 0表示无锁,1表示偏向锁,锁的
    标识位还是01。
  • 轻量级锁:在轻量级锁中直接开辟 30bit 的空间存放指向栈中锁记录的指针,2bit 存放锁的标志
    位,其锁的标志位为00
  • 重量级锁: 在重量级锁中和轻量级锁一样,30bit 的空间用来存放指向重量级锁的指针,2bit 存放
    锁的标识位为11
  • GC标记: 开辟30bit 的内存空间却没有占用,2bit 空间存放锁标志位为11。

其中无锁和偏向锁的锁标志位都是01,只是在前面的1bit区分了这是无锁状态还是偏向锁状态。

总结扩展

两种同步方式本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。

两个指令的执行是JVM通过调用操作系统的互斥原语mutex来实现,被阻塞的线程会被挂起、等待重新调度,会导致“用户态和内核态”两个态之间来回切换,对性能有较大影响。

synchronized关键字经过Javac编译之后,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令。这两个字节码指令都需要一个reference类型的参数来指明要锁定和解锁的对象

如果Java源码中的synchronized明确指定了对象参数,那就以这个对象的引用作为reference。如果没有明确指定,那将根据synchronized修饰的方法类型(如实例方法或类方法),来决定是取代码所在的对象实例还是取类型对应的Class对象来作为线程要持有的锁。

以上是关于Synchronized原理的主要内容,如果未能解决你的问题,请参考以下文章

Synchronized原理

synchronized实现原理

synchronized 实现原理

Synchronized关键字原理

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

Synchronized的底层原理