synchronized最全面的理解

2020-08-09  本文已影响0人  代码能给我讲一下么

什么是synchronized?

synchronized是java提供的一个关键字。可以用来修饰一个方法,一段代码块,来达到一个锁的作用。

synchronized有什么用,该如何使用?

当被synchronized修饰时,表明这个方法或这段代码同一时刻只能由一个线程执行到,其他想要执行相同方法的线程必须等待,直到之前的线程成功释放锁(执行完后自动释放)之后才可以执行。
使用方法如下:

public class SynDemo {
    public  String str; // synchronized不能修饰类和属性
    /*
        synchronized修饰静态方法
        作用范围:整个类
     */
    public synchronized static void fun1(){
        // TODO
    }

    /*
        synchronized修饰普通方法
        作用范围:一个实例对象
     */
    public synchronized void fun2(){
        // TODO
    }

    /*
         synchronized修饰代码块
         作用范围:指定代码块
     */
    public String fun3(){
        String name = "fun3";
        synchronized (this){
            //todo
        }
        return name;
    }
}

那使用的地方不同,达到的效果有什么不同呢?

synchronized的实现原理。

在了解synchronized的实现原理之前,我们需要先对对象的内存布局有个基本了解。对象存储的布局可以分为3块区域,对象头,实例数据和对齐填充。
而HotSpot虚拟机的对象头包括两部分信息:

由于对象头的信息是与对象自身定义的数据没有关系的额外存储成本,因此考虑到JVM的空间效率,Mark Word 被设计成为一个非固定的数据结构,以便存储更多有效的数据,它会根据对象本身的状态复用自己的存储空间,如32位JVM下,除了上述列出的Mark Word默认存储结构外,还有如下可能变化的结构:

mark word.png
通过上图发现重量级锁的标志为10,并且有个指向重量级锁的指针,指针指向的是monitor对象(也称为管程或监视器锁)的起始地址。每个对象都存在着一个 monitor 与之关联。

了解了对象头之后,我们对上诉的代码进行javap -v查看反汇编后的字节码

 public synchronized void fun2();
    descriptor: ()V
    flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 19: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lcom/wchao/jbasic/juc/SynDemo;

 public java.lang.String fun3();
    descriptor: ()Ljava/lang/String;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: ldc           #2                  // String fun3
         2: astore_1
         3: aload_0
         4: dup
         5: astore_2
         6: monitorenter
         7: aload_2
         8: monitorexit
         9: goto          17
        12: astore_3
        13: aload_2
        14: monitorexit
        15: aload_3
        16: athrow
        17: aload_1
        18: areturn

根据上述字节码可以看出当synchronized修饰方法时,jvm是通过添加一个ACC_SYNCHRONIZED访问标志,而修饰代码块时是通过monitorenter和monitorexit指令来实现的。

上面的实现方法主要是指重量级锁的实现,即监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock来实现的,而操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,效率低下。所以Java 6之后,为了减少获得锁和释放锁所带来的性能消耗,引入了偏向锁,和轻量级锁等。
锁一共有四种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态随着竞争情况逐渐升级。为了提高获得锁和释放锁的效率,锁可以升级但不能降级,意味着偏向锁升级为轻量级锁后不能降级为偏向锁。
我们结合对象头的mark word理解下偏向锁和轻量级锁。

2.轻量级锁

线程在执行同步块之前,JVM会先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头的MarkWord复制到锁记录中,即Displaced Mark Word。然后线程会尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁。如果失败,表示其他线程在竞争锁,当前线程使用自旋来获取锁(自旋锁)。当自旋次数达到一定次数时,锁就会升级为重量级锁。轻量级锁解锁时,会使用CAS操作将Displaced Mark Word替换回到对象头,如果成功,表示没有竞争发生。如果失败,表示当前锁存在竞争,锁已经被升级为重量级锁,则会释放锁并唤醒等待的线程。

轻量级锁
上一篇下一篇

猜你喜欢

热点阅读