zoukankan      html  css  js  c++  java
  • synchronized

    一、synchronized介绍

    线程安全问题的主要诱因

      存在共享数据(也称临界资源)

      存在多条线程共同操作这些数据

    解决问题的根本方法:

      同一时刻有且只有一个线程操作共享数据,其它线程必须等待该线程处理完数据后再对共享数据进行操作。

    互斥锁的特性

    互斥性: 即在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程的协调机制,这样在同一时间只有一个线程对需要同步的代码块(复合操作)进行访问。互斥性也称操作的原子性。

    可见性:必须确保在锁被释放之前,对共享变量所做的修改,对应随后获得该锁的另一个线程是可见的(即获得锁的同时应获得最新共享变量的值),否则另一个线程可能是在本地缓存某个副本上继续操作,从而引起不一致。

    synchronized锁的不是代码,锁的都是对象。

    根据获取锁的分类: 获取对象锁和获取类锁

    获取对象锁的两种用法

    1、同步代码块( synchronized(this),  synchronized(类实例对象) ),锁是括号() 中的实例对象。

    2、同步非静态方法( synchronized method) ,锁是当前对象的实例对象

    获取类锁的两种用法

    1、同步代码块(synchronized(类.class)), 锁的是小括号()中的类对象(Class对象).

    2、同步静态方法 (synchronized static method ) , 锁是当前对象的类对象 (Class对象)

    二、synchronized底层实现原理

    实现synchronized基础

    Java对象头

    Monitor

    对象在内存中的布局

    对象头

    实例数据

    对齐填充

    对象头的结构

    Mark Word

    Monitor: 每个Java对象天生自带了一把看不见的锁

    进入源码: http://hg.openjdk.java.net/jdk8u/hs-dev/hotspot/file/ae5624088d86/src/share/vm/runtime/objectMonitor.hpp

    可以发现里面有两个队列 _WaitSet 和 _EntryList

     _owner 指向持有objectMonitor的线程

    下面从字节码的角度查看synchronize

    创建类

    public class SyncBlockAndMethod {
    
        public void syncsTask(){
            synchronized (this){
                System.out.println("Hello");
            }
        }
    
        public synchronized void syncTask(){
            System.out.println("Hello again");
        }
    
    }
    

      然后编译成class文件

    javac SyncBlockAndMethod.java

    查看字节码

    javap -verbose SyncBlockAndMethod.class

    Classfile /xxx/src/thread/SyncBlockAndMethod.class
      Last modified 2019-12-29; size 613 bytes
      MD5 checksum 9aa751fc8ed2cf7d372724572edfb1a8
      Compiled from "SyncBlockAndMethod.java"
    public class thread.SyncBlockAndMethod
      SourceFile: "SyncBlockAndMethod.java"
      minor version: 0
      major version: 51
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #7.#20         //  java/lang/Object."<init>":()V
       #2 = Fieldref           #21.#22        //  java/lang/System.out:Ljava/io/PrintStream;
       #3 = String             #23            //  Hello
       #4 = Methodref          #24.#25        //  java/io/PrintStream.println:(Ljava/lang/String;)V
       #5 = String             #26            //  Hello again
       #6 = Class              #27            //  thread/SyncBlockAndMethod
       #7 = Class              #28            //  java/lang/Object
       #8 = Utf8               <init>
       #9 = Utf8               ()V
      #10 = Utf8               Code
      #11 = Utf8               LineNumberTable
      #12 = Utf8               syncsTask
      #13 = Utf8               StackMapTable
      #14 = Class              #27            //  thread/SyncBlockAndMethod
      #15 = Class              #28            //  java/lang/Object
      #16 = Class              #29            //  java/lang/Throwable
      #17 = Utf8               syncTask
      #18 = Utf8               SourceFile
      #19 = Utf8               SyncBlockAndMethod.java
      #20 = NameAndType        #8:#9          //  "<init>":()V
      #21 = Class              #30            //  java/lang/System
      #22 = NameAndType        #31:#32        //  out:Ljava/io/PrintStream;
      #23 = Utf8               Hello
      #24 = Class              #33            //  java/io/PrintStream
      #25 = NameAndType        #34:#35        //  println:(Ljava/lang/String;)V
      #26 = Utf8               Hello again
      #27 = Utf8               thread/SyncBlockAndMethod
      #28 = Utf8               java/lang/Object
      #29 = Utf8               java/lang/Throwable
      #30 = Utf8               java/lang/System
      #31 = Utf8               out
      #32 = Utf8               Ljava/io/PrintStream;
      #33 = Utf8               java/io/PrintStream
      #34 = Utf8               println
      #35 = Utf8               (Ljava/lang/String;)V
    {
      public thread.SyncBlockAndMethod();
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 6: 0
    
      public void syncsTask();
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=3, args_size=1
             0: aload_0
             1: dup
             2: astore_1
             3: monitorenter
             4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
             7: ldc           #3                  // String Hello
             9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            12: aload_1
            13: monitorexit
            14: goto          22
            17: astore_2
            18: aload_1
            19: monitorexit
            20: aload_2
            21: athrow
            22: return
          Exception table:
             from    to  target type
                 4    14    17   any
                17    20    17   any
          LineNumberTable:
            line 9: 0
            line 10: 4
            line 11: 12
            line 12: 22
          StackMapTable: number_of_entries = 2
               frame_type = 255 /* full_frame */
              offset_delta = 17
              locals = [ class thread/SyncBlockAndMethod, class java/lang/Object ]
              stack = [ class java/lang/Throwable ]
               frame_type = 250 /* chop */
              offset_delta = 4
    
    
      public synchronized void syncTask();
        flags: ACC_PUBLIC, ACC_SYNCHRONIZED
        Code:
          stack=2, locals=1, args_size=1
             0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
             3: ldc           #5                  // String Hello again
             5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
             8: return
          LineNumberTable:
            line 15: 0
            line 16: 8
    }
    

      可以发现有monitorenter 和monitorexit 两条指令

    另外一个方法,没有monitorenter 和monitorexit 两条指令。可以看到ACC_SYNCHRONIZED的访问标志,用来区分一个方法是否是同步方法。当发现有同步标志,执行线程将持有Monitor。

    为什么会对synchronized嗤之以鼻?

    早期版本中,synchronized属于重量级锁,依赖于Mutex Lock实现

    线程之间的切换需要从用户态转换为核心态,开销较大

    Java6以后,synchronized性能得到了很大的提升

    自旋锁和自适应自旋锁

    自旋锁:

      许多情况下,共享数据的锁定状态持续时间较短,切换线程不值得

      通过让线程执行忙循环等待锁的释放,不让出CPU

      缺点: 若锁被其它线程长时间占用,会带来许多性能上的开销

    自适应自旋锁

      自旋的次数不再固定

      由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定。

    synchronized的四种状态

    无锁,偏向锁,轻量级锁, 重量级锁

    锁膨胀方向: 无锁 -> 偏向锁  -> 轻量级锁 -> 重量级锁

    偏向锁: 减少同一线程获取锁的代价

    大多数情况下,锁不存在多线程竞争,总是由同一线程多次获得

    核心思想:

    如果一个线程获得了锁,那么锁就进入了偏向模式,此时Mark Word的结构也变为偏向锁结构,当该线程再次请求锁时,无需再做任何同步操作,即获取锁的过程只需要检查Mark Word的锁标记位为偏向锁以及当前线程Id等于Mark Word的ThreadId即可,这样就省去了大量有关锁申请的操作。

    不适用于锁竞争比较激烈的多线程场合。

    轻量级锁

      轻量级锁是由偏向锁升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就好升级为轻量级锁。

      适用的场景: 线程交替执行同步块

      若存在同一时间访问同一锁的情况,就好导致轻量级锁膨胀为重量级锁。

    偏向锁、轻量级锁、重量级锁的汇总

  • 相关阅读:
    H5 俄罗斯方块Demo
    HTML5 Web Workers
    H5 基于Web Storage 的客户端留言板
    H5 百度一下,你就知道
    H5 71-网易注册界面4
    H5 70-清除浮动方式五
    H5 69-清除浮动方式四
    H5 68-伪元素选择器
    H5 67-清除浮动方式三
    H5 66-清除浮动方式二
  • 原文地址:https://www.cnblogs.com/linlf03/p/12115973.html