• 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即可,这样就省去了大量有关锁申请的操作。

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

    轻量级锁

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

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

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

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

  • 相关阅读:
    java 继承(下)
    java继承
    java代码封装与编译
    使用Access-Control-Allow-Origin解决跨域
    java (基本语法)
    ZendStudio如何汉化
    如何让数据库在每天的某一个时刻自动执行某一个存储过程或者某一个sql语句
    百度地图不用密匙也可以使用
    .net在当前日期的基础上加一天
    当你的IIS需要运行ASP网站时,需要这样配置下你的IIS
  • 原文地址:https://www.cnblogs.com/linlf03/p/12115973.html
Copyright © 2020-2023  润新知