深入了解Synchronized原理

news2024/9/22 10:01:29

深入了解Synchronized原理

  • 第一章:并发编程中的三个问题
    • 1.1 可见性
    • 1.2 原子性
    • 1.3 有序性
  • 第二章:Java内存模型(JMM)
    • 2.1 计算机结构简介
    • 2.2 Java内存模型 Java Memory Molde
  • 第三章:synchronized保证三大特性
    • 3.1 synchronized保证原子性
    • 3.2 synchronized保证可见性
    • 3.3 synchronized有序性
  • 4. synchronized不可中断特性
  • 5. 通过javap学习synchronized原理
    • 5.1 同步代码块
    • 5.2 同步方法
    • 5.3 小结
  • Synchronized和Lock的区别
  • 6. 深入JVM源码monitor监视器锁

第一章:并发编程中的三个问题

1.1 可见性

可见性(Visibility):是指一个线程对共享变量进行修改,另一个先立即得到修改后的最新值。

可见性演示
案例演示:一个线程根据boolean类型的标记flag, while循环,另一个线程改变这个flag变量的值,另一个线程并不会停止循环。

 /**
     * 案例演示:
     * 一个线程对共享变量的修改,另一个线程不能立即得到最新值
     */
    public class Test01Visibility {
        // 多个线程都会访问的数据,我们称为线程的共享数据
        private static boolean run = true;

        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(() -> {
                while (run) {
                }
            });
            t1.start();
            Thread.sleep(1000);
            Thread t2 = new Thread(() -> {
                run = false;
                System.out.println("时间到,线程2设置为false");
            });
            t2.start();
        }
    }

1.2 原子性

原子性(Atomicity):在一次或多次操作中,要么所有的操作都执行并且不会受其他因素干扰而中断,要么所有的操作都不执行。

// 多次运行会发现问题
public class CleanCode {
    private static int number = 0;

    public static void main(String[] args) throws InterruptedException {
        Runnable increment = () -> {
            for (int i = 0; i < 1000; i++) {
                number++;
            }
        };
        List<Thread> ts = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Thread t = new Thread(increment);
            t.start();
            ts.add(t);
        }
        for (Thread t : ts) {
            t.join();
        }
        System.out.println("number = " + number);
    }
}

在这里插入图片描述

反汇编target中thread的CleanCode.calss文件
javap -v -p CleanCode.class 是一个 Java 反编译命令,下面来详细解释一下每个部分的含义:
javap:这是 Java 提供的一个用于反编译的工具命令。
-v:这个选项表示输出详细信息,包括常量池等更多的细节。
-p:表示显示所有成员(包括私有成员)的信息。
当执行这个命令时,它会对名为 CleanCode.class 的字节码文件进行反编译,并按照详细且包括私有成员的方式输出相关的信息。这对于深入了解类的结构、方法签名、字段等非常有用,例如可以查看方法的指令序列、常量池的内容等。比如,如果 CleanCode 类中有一个私有方法 private void secretMethod(),使用 -p 选项就能获取到关于这个方法的反编译信息。

(base) ➜  thread git:(master) ✗ javap -v -p  CleanCode.class
Classfile /Users/fanzhen/Documents/java-base-review/target/classes/src/main/java/thread/CleanCode.class
  Last modified 2024-8-5; size 2272 bytes
  MD5 checksum b99cdb5d0d673bb80ce1131eb988c984
  Compiled from "CleanCode.java"
public class src.main.java.thread.CleanCode
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
    #1 = Methodref          #23.#56       // java/lang/Object."<init>":()V
    #2 = InvokeDynamic      #0:#61        // #0:run:()Ljava/lang/Runnable;
    #3 = Class              #62           // java/util/ArrayList
    #4 = Methodref          #3.#56        // java/util/ArrayList."<init>":()V
    #5 = Class              #63           // java/lang/Thread
    #6 = Methodref          #5.#64        // java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
    #7 = Methodref          #5.#65        // java/lang/Thread.start:()V
    #8 = InterfaceMethodref #66.#67       // java/util/List.add:(Ljava/lang/Object;)Z
    #9 = InterfaceMethodref #66.#68       // java/util/List.iterator:()Ljava/util/Iterator;
   #10 = InterfaceMethodref #69.#70       // java/util/Iterator.hasNext:()Z
   #11 = InterfaceMethodref #69.#71       // java/util/Iterator.next:()Ljava/lang/Object;
   #12 = Methodref          #5.#72        // java/lang/Thread.join:()V
   #13 = Fieldref           #73.#74       // java/lang/System.out:Ljava/io/PrintStream;
   #14 = Class              #75           // java/lang/StringBuilder
   #15 = Methodref          #14.#56       // java/lang/StringBuilder."<init>":()V
   #16 = String             #76           // number =
   #17 = Methodref          #14.#77       // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #18 = Fieldref           #22.#78       // src/main/java/thread/CleanCode.number:I
   #19 = Methodref          #14.#79       // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   #20 = Methodref          #14.#80       // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #21 = Methodref          #81.#82       // java/io/PrintStream.println:(Ljava/lang/String;)V
   #22 = Class              #83           // src/main/java/thread/CleanCode
   #23 = Class              #84           // java/lang/Object
   #24 = Utf8               number
   #25 = Utf8               I
   #26 = Utf8               <init>
   #27 = Utf8               ()V
   #28 = Utf8               Code
   #29 = Utf8               LineNumberTable
   #30 = Utf8               LocalVariableTable
   #31 = Utf8               this
   #32 = Utf8               Lsrc/main/java/thread/CleanCode;
   #33 = Utf8               main
   #34 = Utf8               ([Ljava/lang/String;)V
   #35 = Utf8               t
   #36 = Utf8               Ljava/lang/Thread;
   #37 = Utf8               i
   #38 = Utf8               args
   #39 = Utf8               [Ljava/lang/String;
   #40 = Utf8               increment
   #41 = Utf8               Ljava/lang/Runnable;
   #42 = Utf8               ts
   #43 = Utf8               Ljava/util/List;
   #44 = Utf8               LocalVariableTypeTable
   #45 = Utf8               Ljava/util/List<Ljava/lang/Thread;>;
   #46 = Utf8               StackMapTable
   #47 = Class              #85           // java/lang/Runnable
   #48 = Class              #86           // java/util/List
   #49 = Class              #87           // java/util/Iterator
   #50 = Utf8               Exceptions
   #51 = Class              #88           // java/lang/InterruptedException
   #52 = Utf8               lambda$main$0
   #53 = Utf8               <clinit>
   #54 = Utf8               SourceFile
   #55 = Utf8               CleanCode.java
   #56 = NameAndType        #26:#27       // "<init>":()V
   #57 = Utf8               BootstrapMethods
   #58 = MethodHandle       #6:#89        // invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
   #59 = MethodType         #27           //  ()V
   #60 = MethodHandle       #6:#90        // invokestatic src/main/java/thread/CleanCode.lambda$main$0:()V
   #61 = NameAndType        #91:#92       // run:()Ljava/lang/Runnable;
   #62 = Utf8               java/util/ArrayList
   #63 = Utf8               java/lang/Thread
   #64 = NameAndType        #26:#93       // "<init>":(Ljava/lang/Runnable;)V
   #65 = NameAndType        #94:#27       // start:()V
   #66 = Class              #86           // java/util/List
   #67 = NameAndType        #95:#96       // add:(Ljava/lang/Object;)Z
   #68 = NameAndType        #97:#98       // iterator:()Ljava/util/Iterator;
   #69 = Class              #87           // java/util/Iterator
   #70 = NameAndType        #99:#100      // hasNext:()Z
   #71 = NameAndType        #101:#102     // next:()Ljava/lang/Object;
   #72 = NameAndType        #103:#27      // join:()V
   #73 = Class              #104          // java/lang/System
   #74 = NameAndType        #105:#106     // out:Ljava/io/PrintStream;
   #75 = Utf8               java/lang/StringBuilder
   #76 = Utf8               number =
   #77 = NameAndType        #107:#108     // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #78 = NameAndType        #24:#25       // number:I
   #79 = NameAndType        #107:#109     // append:(I)Ljava/lang/StringBuilder;
   #80 = NameAndType        #110:#111     // toString:()Ljava/lang/String;
   #81 = Class              #112          // java/io/PrintStream
   #82 = NameAndType        #113:#114     // println:(Ljava/lang/String;)V
   #83 = Utf8               src/main/java/thread/CleanCode
   #84 = Utf8               java/lang/Object
   #85 = Utf8               java/lang/Runnable
   #86 = Utf8               java/util/List
   #87 = Utf8               java/util/Iterator
   #88 = Utf8               java/lang/InterruptedException
   #89 = Methodref          #115.#116     // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
   #90 = Methodref          #22.#117      // src/main/java/thread/CleanCode.lambda$main$0:()V
   #91 = Utf8               run
   #92 = Utf8               ()Ljava/lang/Runnable;
   #93 = Utf8               (Ljava/lang/Runnable;)V
   #94 = Utf8               start
   #95 = Utf8               add
   #96 = Utf8               (Ljava/lang/Object;)Z
   #97 = Utf8               iterator
   #98 = Utf8               ()Ljava/util/Iterator;
   #99 = Utf8               hasNext
  #100 = Utf8               ()Z
  #101 = Utf8               next
  #102 = Utf8               ()Ljava/lang/Object;
  #103 = Utf8               join
  #104 = Utf8               java/lang/System
  #105 = Utf8               out
  #106 = Utf8               Ljava/io/PrintStream;
  #107 = Utf8               append
  #108 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #109 = Utf8               (I)Ljava/lang/StringBuilder;
  #110 = Utf8               toString
  #111 = Utf8               ()Ljava/lang/String;
  #112 = Utf8               java/io/PrintStream
  #113 = Utf8               println
  #114 = Utf8               (Ljava/lang/String;)V
  #115 = Class              #118          // java/lang/invoke/LambdaMetafactory
  #116 = NameAndType        #119:#123     // metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #117 = NameAndType        #52:#27       // lambda$main$0:()V
  #118 = Utf8               java/lang/invoke/LambdaMetafactory
  #119 = Utf8               metafactory
  #120 = Class              #125          // java/lang/invoke/MethodHandles$Lookup
  #121 = Utf8               Lookup
  #122 = Utf8               InnerClasses
  #123 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #124 = Class              #126          // java/lang/invoke/MethodHandles
  #125 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #126 = Utf8               java/lang/invoke/MethodHandles
{
  private static int number;
    descriptor: I
    flags: ACC_PRIVATE, ACC_STATIC

  public src.main.java.thread.CleanCode();
    descriptor: ()V
    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
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lsrc/main/java/thread/CleanCode;

  public static void main(java.lang.String[]) throws java.lang.InterruptedException;
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=5, args_size=1
         0: invokedynamic #2,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
         5: astore_1
         6: new           #3                  // class java/util/ArrayList
         9: dup
        10: invokespecial #4                  // Method java/util/ArrayList."<init>":()V
        13: astore_2
        14: iconst_0
        15: istore_3
        16: iload_3
        17: iconst_5
        18: if_icmpge     51
        21: new           #5                  // class java/lang/Thread
        24: dup
        25: aload_1
        26: invokespecial #6                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
        29: astore        4
        31: aload         4
        33: invokevirtual #7                  // Method java/lang/Thread.start:()V
        36: aload_2
        37: aload         4
        39: invokeinterface #8,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
        44: pop
        45: iinc          3, 1
        48: goto          16
        51: aload_2
        52: invokeinterface #9,  1            // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
        57: astore_3
        58: aload_3
        59: invokeinterface #10,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
        64: ifeq          86
        67: aload_3
        68: invokeinterface #11,  1           // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
        73: checkcast     #5                  // class java/lang/Thread
        76: astore        4
        78: aload         4
        80: invokevirtual #12                 // Method java/lang/Thread.join:()V
        83: goto          58
        86: getstatic     #13                 // Field java/lang/System.out:Ljava/io/PrintStream;
        89: new           #14                 // class java/lang/StringBuilder
        92: dup
        93: invokespecial #15                 // Method java/lang/StringBuilder."<init>":()V
        96: ldc           #16                 // String number =
        98: invokevirtual #17                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
       101: getstatic     #18                 // Field number:I
       104: invokevirtual #19                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
       107: invokevirtual #20                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
       110: invokevirtual #21                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       113: return
      LineNumberTable:
        line 10: 0
        line 15: 6
        line 16: 14
        line 17: 21
        line 18: 31
        line 19: 36
        line 16: 45
        line 21: 51
        line 22: 78
        line 23: 83
        line 24: 86
        line 25: 113
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           31      14     4     t   Ljava/lang/Thread;
           16      35     3     i   I
           78       5     4     t   Ljava/lang/Thread;
            0     114     0  args   [Ljava/lang/String;
            6     108     1 increment   Ljava/lang/Runnable;
           14     100     2    ts   Ljava/util/List;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
           14     100     2    ts   Ljava/util/List<Ljava/lang/Thread;>;
      StackMapTable: number_of_entries = 4
        frame_type = 254 /* append */
          offset_delta = 16
          locals = [ class java/lang/Runnable, class java/util/List, int ]
        frame_type = 250 /* chop */
          offset_delta = 34
        frame_type = 252 /* append */
          offset_delta = 6
          locals = [ class java/util/Iterator ]
        frame_type = 250 /* chop */
          offset_delta = 27
    Exceptions:
      throws java.lang.InterruptedException

  private static void lambda$main$0();
    descriptor: ()V
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=1, args_size=0
         0: iconst_0
         1: istore_0
         2: iload_0
         3: sipush        1000
         6: if_icmpge     23
         9: getstatic     #18                 // Field number:I
        12: iconst_1
        13: iadd
        14: putstatic     #18                 // Field number:I
        17: iinc          0, 1
        20: goto          2
        23: return
      LineNumberTable:
        line 11: 0
        line 12: 9
        line 11: 17
        line 14: 23
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            2      21     0     i   I
      StackMapTable: number_of_entries = 2
        frame_type = 252 /* append */
          offset_delta = 2
          locals = [ int ]
        frame_type = 250 /* chop */
          offset_delta = 20

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: iconst_0
         1: putstatic     #18                 // Field number:I
         4: return
      LineNumberTable:
        line 7: 0
}
SourceFile: "CleanCode.java"
InnerClasses:
     public static final #121= #120 of #124; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #58 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #59 ()V
      #60 invokestatic src/main/java/thread/CleanCode.lambda$main$0:()V
      #59 ()V

number++ 对应下面的数据,可以看到number++是由4条语句组成的
在这里插入图片描述

1.3 有序性

有序性(Ordering):是指程序中代码的执行顺序,Java在编译时和运行时会对代码进行优化,会导致程序最终的执行顺序不一定就是我们编写代码时的顺序。

 		<!-- jcstress 核心包 -->
        <dependency>
            <groupId>org.openjdk.jcstress</groupId>
            <artifactId>jcstress-core</artifactId>
            <version>0.3</version>
        </dependency>
        <!-- jcstress测试用例包 -->
        <dependency>
            <groupId>org.openjdk.jcstress</groupId>
            <artifactId>jcstress-samples</artifactId>
            <version>0.3</version>
        </dependency>
@JCStressTest
@Outcome(id = {"1", "4"}, expect = Expect.ACCEPTABLE, desc = "ok") // 定义测试结果为可接受的情况,id为1或4
@Outcome(id = "0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "danger") // 定义测试结果为有趣但可接受的情况,id为0
@State // 表明该类的状态可以被JCStress测试框架使用
public class CleanCode {
    int num = 0; // 初始化一个整型变量num,用于测试数据竞争
    boolean ready = false; // 初始化一个布尔型变量ready,用于控制测试线程的执行

    /**
     * actor1方法是测试线程之一,根据ready的值来决定返回值
     * @param r 结果对象,用于存放计算结果
     */
    @Actor
    public void actor1(I_Result r) {
        if (ready) {
            r.r1 = num + num; // 如果ready为true,则计算并赋值r1
        } else {
            r.r1 = 1; // 如果ready为false,则直接赋值r1为1
        }
    }

    /**
     * actor2方法是测试线程之二,负责修改全局变量num和ready的值
     * @param r 结果对象,用于存放计算结果
     */
    @Actor
    public void actor2(I_Result r) {
        num = 2; // 修改num的值为2
        ready = true; // 将ready设置为true,通知actor1可以进行计算
    }
}

(base) ➜  java-base-review git:(master)  mvn clean install
(base) ➜  java-base-review git:(master) java -jar target/jcstress.jar

在这里插入图片描述

第二章:Java内存模型(JMM)

2.1 计算机结构简介

冯诺依曼,提出计算机由五大组成部分,输入设备,输出设备存储器,控制器,运算器。
在这里插入图片描述
CPU:中央处理器,是计算机的控制和运算的核心,我们的程序最终都会变成指令让CPU去执行,处理程序中的数据。
内存:我们的程序都是在内存中运行的,内存会保存程序运行时的数据,供CPU处理。
缓存:CPU的运算速度和内存的访问速度相差比较大。这就导致CPU每次操作内存都要耗费很多等待时间。内存的读写速度成为了计算机运行的瓶颈。于是就有了在CPU和主内存之间增加缓存的设计。最靠近CPU的缓存称为L1,然后依次是 L2,L3和主内存,CPU缓存模型如图下图所示。
在这里插入图片描述
CPU Cache分成了三个级别: L1, L2, L3。级别越小越接近CPU,速度也更快,同时也代表着容量越小。

  1. L1是最接近CPU的,它容量最小,例如32K,速度最快,每个核上都有一个L1 Cache。
  2. L2 Cache 更大一些,例如256K,速度要慢一些,一般情况下每个核上都有一个独立的L2 Cache。
  3. L3 Cache是三级缓存中最大的一级,例如12MB,同时也是缓存中最慢的一级,在同一个CPU插槽之间的核共享一个L3 Cache。

Cache的出现是为了解决CPU直接访问内存效率低下问题的,程序在运行的过程中,CPU接收到指令后,它会最先向CPU中的一级缓存(L1 Cache)去寻找相关的数据,如果命中缓存,CPU进行计算时就可以直接对CPU Cache中的数据进行读取和写人,当运算结束之后,再将CPUCache中的最新数据刷新
到主内存当中,CPU通过直接访问Cache的方式替代直接访问主存的方式极大地提高了CPU 的吞吐能力。但是由于一级缓存(L1 Cache)容量较小,所以不可能每次都命中。这时CPU会继续向下一级的二级缓存(L2 Cache)寻找,同样的道理,当所需要的数据在二级缓存中也没有的话,会继续转向L3
Cache、内存(主存)和硬盘。

2.2 Java内存模型 Java Memory Molde

  1. 主内存
    主内存是所有线程都共享的,都能访问的。所有的共享变量都存储于主内存。
  2. 工作内存
    每一个线程有自己的工作内存,工作内存只存储该线程对共享变量的副本。线程对变量的所有的操作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量,不同线程之间也不能直接访问对方工作内存中的变量。
    在这里插入图片描述
    Java内存模型的作用:Java内存模型是一套在多线程读写共享数据时,对共享数据的可见性、有序性、和原子性的规则和保障。

在这里插入图片描述

第三章:synchronized保证三大特性

synchronized能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果。

synchronized (锁对象) {
// 受保护资源;
}

3.1 synchronized保证原子性

 public class Test01Atomicity {
        private static int number = 0;
        public static void main(String[] args) throws InterruptedException {
            Runnable increment = new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 1000; i++) {
                        synchronized (Test01Atomicity.class) {
                            number++;
                        }
                    }
                }
            };
            ArrayList<Thread> ts = new ArrayList<>();
            for (int i = 0; i < 50; i++) {
                Thread t = new Thread(increment);
                t.start();
                ts.add(t);
            }
            for (Thread t : ts) {
                t.join();
            }
            System.out.println("number = " + number);
        }
    }

在这里插入图片描述

3.2 synchronized保证可见性

案例演示:一个线程根据boolean类型的标记flag, while循环,另一个线程改变这个flag变量的值,另一个线程并不会停止循环。

/**
     案例演示:
     一个线程根据boolean类型的标记flag, while循环,另一个线程改变这个flag变量的值,
     另一个线程并不会停止循环.
     */
    public class Test01Visibility {
        // 多个线程都会访问的数据,我们称为线程的共享数据
        private static boolean run = true;
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(() -> {
                while (run) {
					// 增加对象共享数据的打印,println是同步方法
					// sout里面的方式是synchronized修饰的
                    System.out.println("run = " + run);
                }
            });
            t1.start();
            Thread.sleep(1000);
            Thread t2 = new Thread(() -> {
                run = false;
                System.out.println("时间到,线程2设置为false");
            });
            t2.start();
        }
    }

在这里插入图片描述
synchronized保证可见性的原理,执行synchronized时,会对应lock原子操作会刷新工作内存中共享变量的值。

主内存与工作内存之间的数据交互过程

lock -> read -> load -> use -> assign -> store -> write -> unlock

3.3 synchronized有序性

as-if-serial语义: 不管编译器和CPU如何重排序,必须保证在单线程情况下程序的结果是正确的。

synchronized保证有序性的原理,我们加synchronized后,依然会发生重排序,只不过,我们有同步代码块,可以保证只有一个线程执行同步代码中的代码。保证有序性。

4. synchronized不可中断特性

什么是不可中断:一个线程获得锁后,另一个线程想要获得锁,必须处于阻塞或等待状态,如果第一个线程不释放锁,第二个线程会一直阻塞或等待,不可被中断。

package thread;

public class Uninterruptible {
    private static Object obj = new Object();

    public static void main(String[] args) throws InterruptedException {
        // 1.定义一个Runnable
        Runnable run = () -> {
            // 2.在Runnable定义同步代码块
            synchronized (obj) {
                String name = Thread.currentThread().getName();
                System.out.println(name + "进入同步代码块");
                // 保证不退出同步代码块
                try {
                    Thread.sleep(888888);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        // 3.先开启一个线程来执行同步代码块
        Thread t1 = new Thread(run);
        t1.start();
        Thread.sleep(1000);
        
        // 4.后开启一个线程来执行同步代码块(阻塞状态)
        Thread t2 = new Thread(run);
        t2.start();

        // 5.停止第二个线程
        System.out.println("停止线程前");
        t2.interrupt();
        System.out.println("停止线程后");
        System.out.println(t1.getState());
        System.out.println(t2.getState());
    }
}

结果:
Thread-0进入同步代码块
停止线程前
停止线程后
TIMED_WAITING
BLOCKED

synchronized属于不可被中断
Lock的lock方法是不可中断的
Lock的tryLock方法是可中断的

5. 通过javap学习synchronized原理

5.1 同步代码块

public class Demo01 {
    private static Object obj = new Object();
    public static void main(String[] args) {
        synchronized (obj) {
            System.out.println("1");
        }
    }
    public synchronized void test() {
        System.out.println("a");
    }
}
javap -p -v class文件

在这里插入图片描述
synchronized进行加锁的时候会关联一个monitor,monitor才是真正的锁。如果没有monitor,JVM会创建一个monitor对象。
monitor里面有两个重要的成员变量一个是owner:拥有锁的线程。
另一个是recursions:记录获取锁的次数。
当一个线程t1走到synchronized同步代码块的时候,会先找到monitor,如果这个锁没人用,那么monitor的owner就会变成t1,且recursions变为1次,可以理解为(JVM规范中对于monitorenter的描述):

  1. 若monior的进入数为0,线程可以进入monitor,并将monitor的进入数置为1。当前线程成为monitor的owner(所有者)
  2. 若线程已拥有monitor的所有权,允许它重入monitor,则进入monitor的进入数加1
  3. 若其他线程已经占有monitor的所有权,那么当前尝试获取monitor的所有权的线程会被阻塞,直到monitor的进入数变为0,才能重新尝试获取monitor的所有权。

synchronized的锁对象会关联一个monitor,这个monitor不是我们主动创建的,是JVM的线程执行到这个同步代码块,发现锁对象没有monitor就会创建monitor,monitor内部有两个重要的成员变量owner:拥有这把锁的线程,recursions会记录线程拥有锁的次数,当一个线程拥有monitor后其他线程只能等待。

monitorexit

  1. 能执行monitorexit指令的线程一定是拥有当前对象的monitor的所有权的线程。
  2. 执行monitorexit时会将monitor的进入数减1。当monitor的进入数减为0时,当前线程退出monitor,不再拥有monitor的所有权,此时其他被这个monitor阻塞的线程可以尝试去获取这个monitor的所有权.

synchronized 同步代码块中有异常会释放锁。

5.2 同步方法

同步方法在反汇编后,会增加 ACC_SYNCHRONIZED 修饰。会隐式调用monitorenter和monitorexit。在执行同步方法前会调用monitorenter,在执行完同步方法后会调用monitorexit。
在这里插入图片描述

5.3 小结

通过javap反汇编我们看到synchronized使用编程了monitorentor和monitorexit两个指令。每个锁对象都会关联一个monitor(监视器,它才是真正的锁对象),它内部有两个重要的成员变量owner会保存获得锁的线程,recursions会保存线程获得锁的次数,当执行到monitorexit时,recursions会-1,当计数器减到0时这个线程就会释放锁。

Synchronized和Lock的区别

  1. synchronized是关键字,Lock是一个接口。
  2. synchronized会自动释放锁,Lock需要手动释放锁。
  3. synchronized是不可中断的,Lock可以终端也可以不中断。
  4. 通过Lock可以知道线程是否获取到锁了,而synchronized不可以。
  5. synchronized能锁住方法和代码块,Lock只能锁住代码块。
  6. Lock可以使用读锁提高多线程的读效率。
  7. synchronized是非公平锁,像ReentrantLock可以控制是否是公平锁。

6. 深入JVM源码monitor监视器锁

https://www.bilibili.com/video/BV1aJ411V763?p=15&vd_source=240d9002f7c7e3da63cd9a975639409a

// todo

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1982611.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

收藏!AIGC创业者必备,AI绘画商业变现保姆级全攻略

随着AI爆火后&#xff0c;AI绘画也随之兴起&#xff0c;每次都有人问我&#xff0c;AI绘画如何变现。来&#xff0c;既然大家对商业赚钱这一块还是很关心的&#xff0c;那今天给大家分享我正在做的AI绘画的商业项目保姆级攻略&#xff0c;重点会偏向于术。全程很干&#xff0c;…

六、8 TIM编码器计数和测速代码

&#xff08;1&#xff09;所用函数 &#xff08;2&#xff09; 1&#xff09; 上拉输入和下拉输入选择&#xff1a;与外部模块保持一致 若外部模块空闲默认输出高电平&#xff0c;就选择上拉输入&#xff0c;默认输入高电平&#xff1b;若外部模块空闲默认输出低电平&#x…

sql注入——二次注入

二次注入 简介工具环境具体实施 简介 二次注入是一种较为隐蔽的 SQL 注入攻击方式。它并非直接在输入时进行攻击&#xff0c;而是先将恶意数据存储到数据库中&#xff0c;这些数据看似正常。随后&#xff0c;应用程序在后续的操作中&#xff0c;再次使用或处理这些之前存储的恶…

黑马微服务—Docker

Docker 文章目录 Docker1 快速入门1.1 部署MySQL1.2 命令解读 2 Docker基础2.1 常见命令2.2 数据卷2.2.1 数据卷**2.2.2 数据卷命令**2.2.3 挂在本地目录或文件 2.3 自定义镜像2.3.1 镜像结构2.3.2 Dockerfile2.3.3 构建镜像 2.4 容器网络 3 项目部署3.1 部署java项目3.2 部署前…

MySQL 实战 45 讲(01-05)

本文为笔者学习林晓斌老师《MySQL 实战 45 讲》课程的学习笔记&#xff0c;并进行了一定的知识扩充。 sql 查询语句的执行流程 大体来说&#xff0c;MySQL 可以分为 Server 层和存储引擎层两部分。 Server 层包括连接器、查询缓存、分析器、优化器和执行器。 连接器负责接收客…

【第14章】Spring Cloud之Gateway路由断言(IP黑名单)

文章目录 前言一、内置路由断言1. 案例&#xff08;Weight&#xff09;2. 更多断言 二、自定义路由断言1. 黑名单断言2. 全局异常处理3. 应用配置4. 单元测试 总结 前言 Spring Cloud Gateway可以让我们根据请求内容精确匹配到对应路由服务,官方已经内置了很多路由断言,我们也…

天润融通助力车企做好战败线索分析,实现商机转化最大化

激烈的行业竞争&#xff0c;让车企越来越重视战败客户分析。 对于每一个汽车品牌来说&#xff0c;大约会有80%甚至更多的留资顾客未在本店购车&#xff0c;最终成为广义上的战败客户。因此&#xff0c;挖掘战败背后的原因对车企意义重大。 作为大宗商品&#xff0c;汽车的交易…

基于Python的Bilibili视频信息分析与可视化

文章目录 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主项目介绍研究背景研究现状研究目的及意义数据采集及预处理数据清洗数据分析与可视化总结每文一语 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主 项目介绍 …

浅谈用二分和三分法解决问题(c++)

目录 问题引入[NOIP2001 提高组] 一元三次方程求解题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示思路分析AC代码 思考关于二分和三分例题讲解进击的奶牛题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 思路AC代码 平均数题目描述输入格式输出格式样例 …

【Material-UI】Icon Button 组件详解

文章目录 一、基础用法1. 禁用状态 二、大小&#xff08;Sizes&#xff09;1. 小尺寸&#xff08;Small&#xff09;2. 大尺寸&#xff08;Large&#xff09; 三、颜色&#xff08;Colors&#xff09;1. 主题颜色2. 自定义颜色 四、高级用法和最佳实践1. 无障碍性&#xff08;A…

【香橙派系列教程】(七)香橙派下的Python3安装

【七】香橙派下的Python3安装 为接下来的Linux图像识别智能垃圾桶做准备。 图像处理使用京东SDK只支持pyhton和Java接口&#xff0c;目的是引入C语言的Python调用&#xff0c;感受大厂做的算法bug 此接口是人工智能接口&#xff0c;京东识别模型是通过训练后的模型&#xff0c;…

打靶记录7——Hacker_Kid-v1.0.1

靶机下载地址 https://download.vulnhub.com/hackerkid/Hacker_Kid-v1.0.1.ova难度 OSCP 风格的中级难度靶机&#xff08;只需要获取root权限即可&#xff0c;CTF 风格的靶机就还需要获取flag&#xff09; 涉及的攻击方法&#xff1a; 主机发现端口扫描Web信息收集DNS区域传…

数组——对数组进行更加全面的理解

1.数组的概念 数组是一组相同类型元素的集合。数组可分为一维数组和多维数组&#xff0c;多维数组常见的是二维数组。 2.一维数组的创建和初始化 2.1 数组的创建 一维数组的创建的基本语法是&#xff1a; type arr_name[常量值] 例如&#xff0c;我们现在想要存储某个班级…

【C语言】qsort函数的介绍和使用

0. 引言 我们日常生活中经常能碰到需要给一组数据排序的情况&#xff0c;如将班上同学的身高&#xff0c;年龄从大到小排序&#xff0c;平时网上购物时对商品价格从低到高排序等等场景&#xff0c;那么电脑是根据什么程序完成这些排序的&#xff1f;接下来就来给大家介绍一下C语…

上升探索WebKit的奥秘:打造高效、兼容的现代网页应用

嘿&#xff0c;朋友们&#xff01;想象一下&#xff0c;你正在浏览一个超级炫酷的网站&#xff0c;页面加载飞快&#xff0c;布局完美适应你的设备&#xff0c;动画流畅得就像你在看一场好莱坞大片。这一切的背后&#xff0c;有一个神秘的英雄——WebKit。今天&#xff0c;我们…

MySQL数据库——数据库的基本操作

目录 三、数据库的基本操作 1.数据库中库的操作 ①创建数据库 ②字符集和校验规则 ③操纵数据库 ④备份与恢复 2.数据库中表的操作 ①创建表 ②查看表 1> 查看表位于的数据库 2>查看所有表 3>查看表中的数据 4>查看创建表的时候的详细信息 ③修改表 …

如何使用react在画布上实现redo-undo?

To implement undo/redo functionality with React you don’t need to use Konva‘s serialization and deserealization methods. You just need to save a history of all the state changes within your app. There are many ways to do this. It may be simpler do to th…

IoTDB 入门教程 企业篇③——数据同步和增量备份

文章目录 一、前文二、系统架构三、准备两台服务器四、新建任务五、数据同步测试六、遇到的问题 一、前文 IoTDB入门教程——导读 数据库备份与迁移是数据库运维中的核心任务&#xff0c;其重要性不言而喻。确保备份过程既简单快捷又稳定可靠&#xff0c;对于保障数据安全与业务…

会声会影下载免费吗?会声会影2023中文旗舰版下载及配置最低要求

**会声会影2024&#xff1a;引领视频创作新时代的创新之旅** 在数字时代的浪潮中&#xff0c;视频创作已成为连接世界、表达创意的重要方式。随着技术的不断进步&#xff0c;一款名为“会声会影2024”的视频编辑软件横空出世&#xff0c;它不仅继承了前代产品的优秀传统&#…

【STM32】EXTI与NVIC标准库使用框架

本篇博客重点在于标准库函数的理解与使用&#xff0c;搭建一个框架便于快速开发 目录 EXTI简介 EXTI配置 使能AFIO的时钟 配置GPIO端口为外部中断 外部中断初始化 NVIC介绍与配置 NVIC中断优先级分组 NVIC初始化 NVIC框架 EXTI配置图 中断函数 中断函数配置 获取中…