深入了解Synchronized原理

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

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

  • 11
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

boy快快长大

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值