并发编程 ~ synchronized。

并发编程 ~ synchronized。


文章目录


深入学习并发编程中的 synchronized。

并发编程中的三个问题。

可见性。
目标。

学习什么是可见性问题。



可见性概念。

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



可见性演示。

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

package com.geek.synchronize.geek.demo;

/**
 * 可见性。 ~ 一个线程对共享变量的修改,另一个线程不能立即得到最新值。
 *
 * @author geek
 */
public class TestVisibility {

    /**
     * 多个线程都会访问的数据,称为线程的共享数据。
     */
    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();
    }

}

在这里插入图片描述
线程 2 已改为 false,但是程序还在一直运行,没有结束。说明线程 1 还在运行中。

并发编程时,会出现可见性问题,当一个线程对共享变量进行了修改,另外的线程并没有立即看到修改后的最新值。



原子性。
目标。

学习什么是原子性问题。



原子性概念。

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



原子性演示。

案例演示:5 个线程各执行 1000 次 i++; 使用 javap 反汇编 class 文件,得到下面的字节码指令。

package com.geek.synchronize.geek.demo;

import java.util.ArrayList;
import java.util.List;

/**
 * 原子性。 ~ 5 个线程各执行 1000 次 ++i;。
 *
 * @author geek
 */
public class TestAtomicity {

    /**
     * 1、共享变量 iNumber。
     */
    private static int iNumber = 0;

    public static void main(String[] args) throws InterruptedException {
        // 2、对 iNumber 进行 1000 次 ++ 操作。
        Runnable runnable = () -> {
            for (int i = 0; i < 1000; i++) {
                ++iNumber;
            }
        };
        // 3、 使用 5 个线程进行 ++ 操作。
        List<Thread> threadList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(runnable);
            thread.start();
            threadList.add(thread);
        }
        for (Thread thread : threadList) {
            thread.join();
        }
        System.out.println("iNumber = " + iNumber);
    }

}

/*
Connected to the target VM, address: '127.0.0.1:58721', transport: 'socket'
iNumber = 2547
Disconnected from the target VM, address: '127.0.0.1:58721', transport: 'socket'

Process finished with exit code 0
 */

/*
Connected to the target VM, address: '127.0.0.1:58697', transport: 'socket'
iNumber = 5000
Disconnected from the target VM, address: '127.0.0.1:58697', transport: 'socket'

Process finished with exit code 0
 */

其中,对于 ++number 而言(number 为静态变量),实际会产生如下的 JVM 字节码指令。

         9: getstatic     #18                 // Field iNumber:I
        12: iconst_1
        13: iadd
        14: putstatic     #18                 // Field iNumber:I

由此可见 ++number 是由多条语句组成,以上多条指令在一个线程的情况下是不会出问题的,但是在多线程情况下就可能会出现问题。比如一个线程在执行 13: iadd 时,另一个线程又执行 9: getstatic。会导致两次 ++number,实际上只加了 1。

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

  public com.geek.synchronize.geek.demo.TestAtomicity();
    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 11: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/geek/synchronize/geek/demo/TestAtomicity;

  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 iNumber =
        98: invokevirtual #17                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
       101: getstatic     #18                 // Field iNumber: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 20: 0
        line 26: 6
        line 27: 14
        line 28: 21
        line 29: 31
        line 30: 36
        line 27: 45
        line 32: 51
        line 33: 78
        line 34: 83
        line 35: 86
        line 36: 113
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           31      14     4 thread   Ljava/lang/Thread;
           16      35     3     i   I
           78       5     4 thread   Ljava/lang/Thread;
            0     114     0  args   [Ljava/lang/String;
            6     108     1 runnable   Ljava/lang/Runnable;
           14     100     2 threadList   Ljava/util/List;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
           14     100     2 threadList   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
    MethodParameters:
      Name                           Flags
      args

  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 iNumber:I
        12: iconst_1
        13: iadd
        14: putstatic     #18                 // Field iNumber:I
        17: iinc          0, 1
        20: goto          2
        23: return
      LineNumberTable:
        line 21: 0
        line 22: 9
        line 21: 17
        line 24: 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 iNumber:I
         4: return
      LineNumberTable:
        line 16: 0
}
SourceFile: "TestAtomicity.java"
InnerClasses:
     public static final #122= #121 of #125; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles    
BootstrapMethods:
  0: #59 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/inv
oke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #60 ()V
      #61 invokestatic com/geek/synchronize/geek/demo/TestAtomicity.lambda$main$0:()V
      #60 ()V



小结。

并发编程时,会出现原子性问题,当一个线程对共享变量操作到一半时,另外的线程也有可能来操作共享变量,干扰了前一个线程的操作。



有序性。
目标。

学习什么是有序性问题。



有序性概念。

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

public static void main(String[] args) {
        int a = 10;
        int b = 20;
    }


有序性演示。

jcstress 是 java 并发压测工具。
https://wiki.openjdk.java.net/display/CodeTools/jcstress

修改 pom 文件,添加依赖。

<!-- https://mvnrepository.com/artifact/org.openjdk.jcstress/jcstress-core -->
<dependency>
    <groupId>org.openjdk.jcstress</groupId>
    <artifactId>jcstress-core</artifactId>
    <version>0.15</version>
    <scope>test</scope>
</dependency>

package com.geek.synchronize.geek.demo;

import org.openjdk.jcstress.annotations.*;
import org.openjdk.jcstress.infra.results.I_Result;

/**
 * 有序性。
 *
 * @author geek
 */
@JCStressTest
@Outcome(id = {"1", "4"}, expect = Expect.ACCEPTABLE, desc = "ok")
@Outcome(id = "0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "danger")
@State
public class TestOrderliness {

    private Integer num = 0;
    private Boolean ready = false;

    /**
     * 线程 1 执行的代码。
     * // @Actor 多个线程执行该方法。
     *
     * @param iResult
     */
    @Actor
    public void actor1(I_Result iResult) {
        if (this.ready) {
            iResult.r1 = this.num + this.num;
        } else {
            iResult.r1 = 1;
        }
    }

    /**
     * 线程 2 执行的代码。
     * // @Actor 多个线程执行该方法。
     *
     * @param iResult
     */
    @Actor
    public void actor2(I_Result iResult) {
        this.num = 2;
        this.ready = true;
    }

}

I_Result 是一个对象,有一个属性 r1 用来保存结果,在多线程情况下可能出现几种结果?

情况 1:线程 1 先执行 actor1,这时 ready = false,所以进入 else 分支结果为 1。

情况 2:线程 2 执行到 actor2,执行了 num = 2; 和 ready = true;,线程 1 执行,这回进入 if 分支,结果为 4。

情况 3:线程 2 先执行 actor2,只执行 num = 2;但没来得及执行 ready = true;,线程 1 执行,还是进入 else 分支,结果为 1。

还有一种结果 0。

运行测试。

mvn clean install
java -jar target/jcstress.jar

Java Concurrency Stress Tests
---------------------------------------------------------------------------------
Rev: 081b1492fcc0, built by shade with 9-ea at 2017-03-29T11:30:22Z

Probing what VM modes are available:
 (failures are non-fatal, but may miss some interesting cases)

----- [OK] [-Xint]
----- [OK] [-XX:TieredStopAtLevel=1]
----- [OK] []
----- [N/A] [-XX:+UnlockDiagnosticVMOptions, -XX:+StressLCM, -XX:+StressGCM]
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
Error: VM option 'StressLCM' is develop and is available only in debug version of VM.


----- [OK] [-XX:-TieredCompilation]
----- [N/A] [-XX:-TieredCompilation, -XX:+UnlockDiagnosticVMOptions, -XX:+StressLCM, -XX:+StressGCM]
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
Error: VM option 'StressLCM' is develop and is available only in debug version of VM.



Initializing and probing the target VM: 
 (all failures are non-fatal, but may affect testing accuracy)

----- [OK] Unlocking diagnostic VM options
----- [OK] Trimming down the number of compiler threads
----- [OK] Trimming down the number of parallel GC threads
----- [OK] Trimming down the number of concurrent GC threads
----- [OK] Trimming down the number of G1 concurrent refinement GC threads
----- [FAILED] Testing @Contended works on all results and infra objects
Exception in thread "main" java.lang.IllegalStateException: G:.lyfGeek.IdeaProjects.synchronized-geek.target.classes.com.geek.synchronize.geek.SynchronizedGeekApplication
	at org.openjdk.jcstress.util.Reflections.getClasses(Reflections.java:66)
	at org.openjdk.jcstress.vm.ContendedTestMain.main(ContendedTestMain.java:43)
Caused by: java.lang.ClassNotFoundException: G:.lyfGeek.IdeaProjects.synchronized-geek.target.classes.com.geek.synchronize.geek.SynchronizedGeekApplication
	at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:348)
	at org.openjdk.jcstress.util.Reflections.getClasses(Reflections.java:64)
	... 1 more

----- [OK] Unlocking Whitebox API for online de-optimization
----- [OK] Testing allocation profiling
----- [FAILED] Trying Thread.onSpinWait
Exception in thread "main" java.lang.NoSuchMethodError: java.lang.Thread.onSpinWait()V
	at org.openjdk.jcstress.vm.ThreadSpinWaitTestMain.main(ThreadSpinWaitTestMain.java:30)


Burning up to figure out the exact CPU count....... done!

  Hardware threads in use/available: 8/8, no yielding.
  Test preset mode: "default"
  Writing the test results to "jcstress-results-2023-02-27-01-15-47.bin.gz"
  Parsing results to "results/"
  Running each test matching "com.geek.synchronize.geek.demo" for 1 forks, 5 iterations, 1000 ms each
  Each JVM would execute at most 5 tests in the row.
  Solo stride size will be autobalanced within [10, 10000] elements, but taking no more than 100 Mb.

      [OK] com.geek.synchronize.geek.demo.TestOrderliness
    (fork: #1, iteration #1, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=59987:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             1   ACCEPTABLE_INTERESTING  danger                                                      
               1    10,894,133               ACCEPTABLE  ok                                                          
               4    12,395,636               ACCEPTABLE  ok                                                          

      [OK] com.geek.synchronize.geek.demo.TestOrderliness
      [OK] com.geek.synchronize.geek.demo.TestOrderliness
      [OK] com.geek.synchronize.geek.demo.TestOrderliness
    (fork: #1, iteration #1, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=59987:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8, -XX:-TieredCompilation])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             6   ACCEPTABLE_INTERESTING  danger                                                      
               1    11,472,915               ACCEPTABLE  ok                                                          
               4    17,065,739               ACCEPTABLE  ok                                                          

      [OK] com.geek.synchronize.geek.demo.TestOrderliness
      [OK] com.geek.synchronize.geek.demo.TestOrderliness
      [OK] com.geek.synchronize.geek.demo.TestOrderliness
      [OK] com.geek.synchronize.geek.demo.TestOrderliness
      [OK] com.geek.synchronize.geek.demo.TestOrderliness
    (fork: #1, iteration #3, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=59987:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0            20   ACCEPTABLE_INTERESTING  danger                                                      
               1    17,266,219               ACCEPTABLE  ok                                                          
               4     9,377,961               ACCEPTABLE  ok                                                          

      [OK] com.geek.synchronize.geek.demo.TestOrderliness
      [OK] com.geek.synchronize.geek.demo.TestOrderliness
    (fork: #1, iteration #3, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=59987:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8, -XX:-TieredCompilation])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             1   ACCEPTABLE_INTERESTING  danger                                                      
               1    15,206,050               ACCEPTABLE  ok                                                          
               4    10,328,159               ACCEPTABLE  ok                                                          

      [OK] com.geek.synchronize.geek.demo.TestOrderliness
      [OK] com.geek.synchronize.geek.demo.TestOrderliness
    (fork: #1, iteration #4, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=59987:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0            17   ACCEPTABLE_INTERESTING  danger                                                      
               1    15,548,577               ACCEPTABLE  ok                                                          
               4     9,100,746               ACCEPTABLE  ok                                                          

      [OK] com.geek.synchronize.geek.demo.TestOrderliness
      [OK] com.geek.synchronize.geek.demo.TestOrderliness
      [OK] com.geek.synchronize.geek.demo.TestOrderliness
      [OK] com.geek.synchronize.geek.demo.TestOrderliness
    (fork: #1, iteration #5, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=59987:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             2   ACCEPTABLE_INTERESTING  danger                                                      
               1    11,336,125               ACCEPTABLE  ok                                                          
               4     9,643,203               ACCEPTABLE  ok                                                          

      [OK] com.geek.synchronize.geek.demo.TestOrderliness
      [OK] com.geek.synchronize.geek.demo.TestOrderliness
    (fork: #1, iteration #5, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=59987:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8, -XX:-TieredCompilation])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             1   ACCEPTABLE_INTERESTING  danger                                                      
               1    11,983,617               ACCEPTABLE  ok                                                          
               4    11,824,162               ACCEPTABLE  ok                                                          

      [OK] com.geek.synchronize.geek.demo.TestOrderliness
(ETA:        now) (Rate: 7.43E+07 samples/sec) (Tests: 1 of 1) (Forks:  4 of 4) (Iterations: 20 of 20; 20 passed, 0 failed, 0 soft errs, 0 hard errs) 

RUN COMPLETE.

RUN RESULTS:
------------------------------------------------------------------------------------------------------------------------

*** INTERESTING tests
  Some interesting behaviors observed. This is for the plain curiosity.

  2 matching test results. 
      [OK] com.geek.synchronize.geek.demo.TestOrderliness
    (JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=59987:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8, -XX:-TieredCompilation])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             8   ACCEPTABLE_INTERESTING  danger                                                      
               1    64,124,503               ACCEPTABLE  ok                                                          
               4    56,683,999               ACCEPTABLE  ok                                                          

      [OK] com.geek.synchronize.geek.demo.TestOrderliness
    (JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=59987:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0            40   ACCEPTABLE_INTERESTING  danger                                                      
               1    57,723,686               ACCEPTABLE  ok                                                          
               4    55,127,314               ACCEPTABLE  ok                                                          


*** FAILED tests
  Strong asserts were violated. Correct implementations should have no assert failures here.

  0 matching test results. 

*** ERROR tests
  Tests break for some reason, other than failing the assert. Correct implementations should have none.

  0 matching test results. 

*** All remaining tests
  Tests that do not fall into any of the previous categories.

  2 matching test results.  Use -v to print them.

------------------------------------------------------------------------------------------------------------------------

HTML report was generated. Look at results/index.html for the complete run results.

Will throw any pending exceptions at this point.
Done.

Process finished with exit code 0



小结。

程序代码在执行过程中的先后顺序,由于 Java 在编译期以及运行期的优化,导致了代码的执行顺序未必就是开发者编写代码时的顺序。



Java 内存模型(JMM)。

在介绍 Java 内存模型之前,先来看一下到底什么是计算机内存模型。

计算机结构。
目标。
  • 学习计算机的主要组成。

  • 学习缓存的作用。



计算机结构简介。

冯诺依曼,提出计算机由五大组成部分,输入设备,输出设备存储器,控制器,运算器。

在这里插入图片描述

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



小结。

计算机的主要组成 CPU,内存,输入设备,输出设备。



Java 内存模型。
目标。

学习 Java 内存模型的概念和作用。

Java 内存模型的概念 Java Memory Molde (Java 内存模型 / JMM),千万不要和 Java 内存结构混淆。

关于“Java内存模型”的权威解释,请参考 https://download.oracle.com/otn-pub/jcp/memory_model1.0-pfd-spec-oth-JSpec/memory_model-1_0-pfd-spec.pdf。

Java 内存模型,是 Java 虚拟机规范中所定义的一种内存模型,Java 内存模型是标准化的,屏蔽掉了底层不同计算机的区别。

Java 内存模型是一套规范,描述了 Java 程序中各种变量(线程共享变量)的访问规则,以及在 JVM 中将变量存储到内存和从内存中读取变量这样的底层细节,具体如下。

  • 主内存。
    主内存是所有线程都共享的,都能访问的。所有的共享变量都存储于主内存。

  • 工作内存。
    每一个线程有自己的工作内存,工作内存只存储该线程对共享变量的副本。线程对变量的所有的操作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量,不同线程之间也不能直接访问对方工作内存中的变量。

在这里插入图片描述



Java内存模型的作用。

Java 内存模型是一套在多线程读写共享数据时,对共享数据的可见性、有序性、和原子性的规则和保障。

synchronized,volatile。



CPU 缓存,内存与 Java 内存模型的关系。

通过对前面的 CPU 硬件内存架构、Java 内存模型以及 Java 多线程的实现原理的了解,我们应该已经意识到,多线程的执行最终都会映射到硬件处理器上进行执行。
但 Java 内存模型和硬件内存架构并不完全一致。对于硬件内存来说只有寄存器、缓存内存、主内存的概念,并没有工作内存和主内存之分,也就是说Java内存模型对内存的划分对硬件内存并没有任何影响,因为 JMM 只是一种抽象的概念,是一组规则,不管是工作内存的数据还是主内存的数据,对于计算机硬件来说都会存储在计算机主内存中,当然也有可能存储到 CPU 缓存或者寄存器中,因此总体上来说,Java 内存模型和计算机硬件内存架构是一个相互交叉的关系,是一种抽象概念划分与真实物理硬件的交叉。

JMM内存模型与CPU硬件内存架构的关系。

在这里插入图片描述



小结。

Java 内存模型是一套规范,描述了 Java 程序中各种变量(线程共享变量)的访问规则,以及在 JVM 中将变量存储到内存和从内存中读取变量这样的底层细节,Java 内存模型是对共享数据的可见性、有序性、和原子性的规则和保障。



主内存与工作内存之间的交互目标。

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

Java 内存模型中定义了以下 8 种操作来完成,主内存与工作内存之间具体的交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存之类的实现细节,虚拟机实现时必须保证下面提及的每一种操作都是原子的、不可再分的。

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

对应如下的流程图。

在这里插入图片描述
注意。

  1. 如果对一个变量执行 lock 操作,将会清空工作内存中此变量的值。

  2. 对一个变量执行 unlock 操作之前,必须先把此变量同步到主内存中。



小结。

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



第三章:synchronized 保证三大特性。

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

synchronized (锁对象) {
	// 受保护资源;
}
synchronized 与原子性。
目标。

学习使用 synchronized 保证原子性的原理。

使用 synchronized 保证原子性。

案例演示:5 个线程各执行 1000 次 i++;

synchronized 保证原子性的原理。

对 number++; 增加同步代码块后,保证同一时间只有一个线程操作number++;。就不会出现安全问题。

package com.geek.synchronize.geek.demo;

import java.util.ArrayList;
import java.util.List;

/**
 * 原子性。 ~ 5 个线程各执行 1000 次 ++i;。
 *
 * @author geek
 */
public class TestAtomicitySynchronized {

    private static final Object OBJECT = new Object();
    /**
     * 1、共享变量 iNumber。
     */
    private static int iNumber = 0;

    public static void main(String[] args) throws InterruptedException {
        // 2、对 iNumber 进行 1000 次 ++ 操作。
        Runnable runnable = () -> {
            for (int i = 0; i < 1000; i++) {
                synchronized (OBJECT) {
                    ++iNumber;
                }
            }
        };
        // 3、 使用 5 个线程进行 ++ 操作。
        List<Thread> threadList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(runnable);
            thread.start();
            threadList.add(thread);
        }
        for (Thread thread : threadList) {
            thread.join();
        }
        System.out.println("iNumber = " + iNumber);
    }

}

/*
Connected to the target VM, address: '127.0.0.1:58697', transport: 'socket'
iNumber = 5000
Disconnected from the target VM, address: '127.0.0.1:58697', transport: 'socket'

Process finished with exit code 0
 */

        14: monitorenter
        15: getstatic     #18                 // Field iNumber:I
        18: iconst_1
        19: iadd
        20: putstatic     #18                 // Field iNumber:I
        23: aload_1
        24: monitorexit
PS G:\lyfGeek\IdeaProjects\synchronized-geek\target\classes\com\geek\synchronize\geek\demo> javap -p -v .\TestAtomicitySynchronized.class
Classfile /G:/lyfGeek/IdeaProjects/synchronized-geek/target/classes/com/geek/synchronize/geek/demo/TestAtomicitySynchronized.class
  Last modified Feb 27, 2023; size 2531 bytes   
  MD5 checksum 0737aeafaeae9d5df1ee5f0a66604297 
  Compiled from "TestAtomicitySynchronized.java"
public class com.geek.synchronize.geek.demo.TestAtomicitySynchronized
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
    #1 = Methodref          #23.#62       // java/lang/Object."<init>":()V
    #2 = InvokeDynamic      #0:#67        // #0:run:()Ljava/lang/Runnable;
    #3 = Class              #68           // java/util/ArrayList
    #4 = Methodref          #3.#62        // java/util/ArrayList."<init>":()V
    #5 = Class              #69           // java/lang/Thread
    #6 = Methodref          #5.#70        // java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
    #7 = Methodref          #5.#71        // java/lang/Thread.start:()V
    #8 = InterfaceMethodref #72.#73       // java/util/List.add:(Ljava/lang/Object;)Z
    #9 = InterfaceMethodref #72.#74       // java/util/List.iterator:()Ljava/util/Iterator;
   #10 = InterfaceMethodref #75.#76       // java/util/Iterator.hasNext:()Z
   #11 = InterfaceMethodref #75.#77       // java/util/Iterator.next:()Ljava/lang/Object;
   #12 = Methodref          #5.#78        // java/lang/Thread.join:()V
   #13 = Fieldref           #79.#80       // java/lang/System.out:Ljava/io/PrintStream;
   #14 = Class              #81           // java/lang/StringBuilder
   #15 = Methodref          #14.#62       // java/lang/StringBuilder."<init>":()V
   #16 = String             #82           // iNumber =
   #17 = Methodref          #14.#83       // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #18 = Fieldref           #24.#84       // com/geek/synchronize/geek/demo/TestAtomicitySynchronized.iNumber:I
   #19 = Methodref          #14.#85       // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   #20 = Methodref          #14.#86       // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #21 = Methodref          #87.#88       // java/io/PrintStream.println:(Ljava/lang/String;)V
   #22 = Fieldref           #24.#89       // com/geek/synchronize/geek/demo/TestAtomicitySynchronized.OBJECT:Ljava/lang/Object;
   #23 = Class              #90           // java/lang/Object
   #24 = Class              #91           // com/geek/synchronize/geek/demo/TestAtomicitySynchronized
   #25 = Utf8               OBJECT
   #26 = Utf8               Ljava/lang/Object;
   #27 = Utf8               iNumber
   #28 = Utf8               I
   #29 = Utf8               <init>
   #30 = Utf8               ()V
   #31 = Utf8               Code
   #32 = Utf8               LineNumberTable
   #33 = Utf8               LocalVariableTable
   #34 = Utf8               this
   #35 = Utf8               Lcom/geek/synchronize/geek/demo/TestAtomicitySynchronized;
   #36 = Utf8               main
   #37 = Utf8               ([Ljava/lang/String;)V
   #38 = Utf8               thread
   #39 = Utf8               Ljava/lang/Thread;
   #40 = Utf8               i
   #41 = Utf8               args
   #42 = Utf8               [Ljava/lang/String;
   #43 = Utf8               runnable
   #44 = Utf8               Ljava/lang/Runnable;
   #45 = Utf8               threadList
   #46 = Utf8               Ljava/util/List;
   #47 = Utf8               LocalVariableTypeTable
   #48 = Utf8               Ljava/util/List<Ljava/lang/Thread;>;
   #49 = Utf8               StackMapTable
   #50 = Class              #92           // java/lang/Runnable
   #51 = Class              #93           // java/util/List
   #52 = Class              #94           // java/util/Iterator
   #53 = Utf8               Exceptions
   #54 = Class              #95           // java/lang/InterruptedException
   #55 = Utf8               MethodParameters
   #56 = Utf8               lambda$main$0
   #57 = Class              #90           // java/lang/Object
   #58 = Class              #96           // java/lang/Throwable
   #59 = Utf8               <clinit>
   #60 = Utf8               SourceFile
   #61 = Utf8               TestAtomicitySynchronized.java
   #62 = NameAndType        #29:#30       // "<init>":()V
   #63 = Utf8               BootstrapMethods
   #64 = MethodHandle       #6:#97        // invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Loo
kup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)
Ljava/lang/invoke/CallSite;
   #65 = MethodType         #30           //  ()V
   #66 = MethodHandle       #6:#98        // invokestatic com/geek/synchronize/geek/demo/TestAtomicitySynchronized.lambda$main$0:()V
   #67 = NameAndType        #99:#100      // run:()Ljava/lang/Runnable;
   #68 = Utf8               java/util/ArrayList
   #69 = Utf8               java/lang/Thread
   #70 = NameAndType        #29:#101      // "<init>":(Ljava/lang/Runnable;)V
   #71 = NameAndType        #102:#30      // start:()V
   #72 = Class              #93           // java/util/List
   #73 = NameAndType        #103:#104     // add:(Ljava/lang/Object;)Z
   #74 = NameAndType        #105:#106     // iterator:()Ljava/util/Iterator;
   #75 = Class              #94           // java/util/Iterator
   #76 = NameAndType        #107:#108     // hasNext:()Z
   #77 = NameAndType        #109:#110     // next:()Ljava/lang/Object;
   #78 = NameAndType        #111:#30      // join:()V
   #79 = Class              #112          // java/lang/System
   #80 = NameAndType        #113:#114     // out:Ljava/io/PrintStream;
   #81 = Utf8               java/lang/StringBuilder
   #82 = Utf8               iNumber =
   #83 = NameAndType        #115:#116     // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #84 = NameAndType        #27:#28       // iNumber:I
   #85 = NameAndType        #115:#117     // append:(I)Ljava/lang/StringBuilder;
   #86 = NameAndType        #118:#119     // toString:()Ljava/lang/String;
   #87 = Class              #120          // java/io/PrintStream
   #88 = NameAndType        #121:#122     // println:(Ljava/lang/String;)V
   #89 = NameAndType        #25:#26       // OBJECT:Ljava/lang/Object;
   #90 = Utf8               java/lang/Object
   #91 = Utf8               com/geek/synchronize/geek/demo/TestAtomicitySynchronized
   #92 = Utf8               java/lang/Runnable
   #93 = Utf8               java/util/List
   #94 = Utf8               java/util/Iterator
   #95 = Utf8               java/lang/InterruptedException
   #96 = Utf8               java/lang/Throwable
   #97 = Methodref          #123.#124     // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lan
g/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/in
voke/CallSite;
   #98 = Methodref          #24.#125      // com/geek/synchronize/geek/demo/TestAtomicitySynchronized.lambda$main$0:()V
   #99 = Utf8               run
  #100 = Utf8               ()Ljava/lang/Runnable;
  #101 = Utf8               (Ljava/lang/Runnable;)V
  #102 = Utf8               start
  #103 = Utf8               add
  #104 = Utf8               (Ljava/lang/Object;)Z
  #105 = Utf8               iterator
  #106 = Utf8               ()Ljava/util/Iterator;
  #107 = Utf8               hasNext
  #108 = Utf8               ()Z
  #109 = Utf8               next
  #110 = Utf8               ()Ljava/lang/Object;
  #111 = Utf8               join
  #112 = Utf8               java/lang/System
  #113 = Utf8               out
  #114 = Utf8               Ljava/io/PrintStream;
  #115 = Utf8               append
  #116 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #117 = Utf8               (I)Ljava/lang/StringBuilder;
  #118 = Utf8               toString
  #119 = Utf8               ()Ljava/lang/String;
  #120 = Utf8               java/io/PrintStream
  #121 = Utf8               println
  #122 = Utf8               (Ljava/lang/String;)V
  #123 = Class              #126          // java/lang/invoke/LambdaMetafactory
  #124 = NameAndType        #127:#131     // metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodTy
pe;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #125 = NameAndType        #56:#30       // lambda$main$0:()V
  #126 = Utf8               java/lang/invoke/LambdaMetafactory
  #127 = Utf8               metafactory
  #128 = Class              #133          // java/lang/invoke/MethodHandles$Lookup
  #129 = Utf8               Lookup
  #130 = Utf8               InnerClasses
  #131 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodTy
pe;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #132 = Class              #134          // java/lang/invoke/MethodHandles
  #133 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #134 = Utf8               java/lang/invoke/MethodHandles
{
  private static final java.lang.Object OBJECT;
    descriptor: Ljava/lang/Object;
    flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL

  private static int iNumber;
    descriptor: I
    flags: ACC_PRIVATE, ACC_STATIC

  public com.geek.synchronize.geek.demo.TestAtomicitySynchronized();
    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 11: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/geek/synchronize/geek/demo/TestAtomicitySynchronized;

  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 iNumber =
        98: invokevirtual #17                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
       101: getstatic     #18                 // Field iNumber: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 21: 0
        line 29: 6
        line 30: 14
        line 31: 21
        line 32: 31
        line 33: 36
        line 30: 45
        line 35: 51
        line 36: 78
        line 37: 83
        line 38: 86
        line 39: 113
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           31      14     4 thread   Ljava/lang/Thread;
           16      35     3     i   I
           78       5     4 thread   Ljava/lang/Thread;
            0     114     0  args   [Ljava/lang/String;
            6     108     1 runnable   Ljava/lang/Runnable;
           14     100     2 threadList   Ljava/util/List;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
           14     100     2 threadList   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
    MethodParameters:
      Name                           Flags
      args

  private static void lambda$main$0();
    descriptor: ()V
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=3, args_size=0
         0: iconst_0
         1: istore_0
         2: iload_0
         3: sipush        1000
         6: if_icmpge     39
         9: getstatic     #22                 // Field OBJECT:Ljava/lang/Object;
        12: dup
        13: astore_1
        14: monitorenter
        15: getstatic     #18                 // Field iNumber:I
        18: iconst_1
        19: iadd
        20: putstatic     #18                 // Field iNumber:I
        23: aload_1
        24: monitorexit
        25: goto          33
        28: astore_2
        29: aload_1
        30: monitorexit
        31: aload_2
        32: athrow
        33: iinc          0, 1
        36: goto          2
        39: return
      Exception table:
         from    to  target type
            15    25    28   any
            28    31    28   any
      LineNumberTable:
        line 22: 0
        line 23: 9
        line 24: 15
        line 25: 23
        line 22: 33
        line 27: 39
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            2      37     0     i   I
      StackMapTable: number_of_entries = 4
        frame_type = 252 /* append */
          offset_delta = 2
          locals = [ int ]
        frame_type = 255 /* full_frame */
          offset_delta = 25
          locals = [ int, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4
        frame_type = 250 /* chop */
          offset_delta = 5

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: new           #23                 // class java/lang/Object
         3: dup
         4: invokespecial #1                  // Method java/lang/Object."<init>":()V
         7: putstatic     #22                 // Field OBJECT:Ljava/lang/Object;
        10: iconst_0
        11: putstatic     #18                 // Field iNumber:I
        14: return
      LineNumberTable:
        line 13: 0
        line 17: 10
}
SourceFile: "TestAtomicitySynchronized.java"
InnerClasses:
     public static final #129= #128 of #132; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles    
BootstrapMethods:
  0: #64 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/inv
oke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #65 ()V
      #66 invokestatic com/geek/synchronize/geek/demo/TestAtomicitySynchronized.lambda$main$0:()V
      #65 ()V



小结。

synchronized 保证原子性的原理,synchronized 保证只有一个线程拿到锁,能够进入同步代码块。



synchronized 与可见性。
目标。

学习使用 synchronized 保证可见性的原理。

使用 synchronized 保证可见性。

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

package com.geek.synchronize.geek.demo;

/**
 * 可见性。 ~ 一个线程对共享变量的修改,另一个线程不能立即得到最新值。
 *
 * @author geek
 */
public class TestVisibility {

    /**
     * 1、创建一个共享变量。
     * 多个线程都会访问的数据,称为线程的共享数据。
     */
    private static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        // 2、创建一条线程不断读取共享变量。
        Thread thread = new Thread(() -> {
            while (flag) {
            }
        });
        thread.start();

        Thread.sleep(2000);

        // 3、创建一条线程修改共享变量。
        Thread thread1 = new Thread(() -> {
            flag = false;
            System.out.println("时间到,线程 2 设置为 false。");
        });
        thread1.start();
    }

}

在这里插入图片描述
线程 2 将 flag = false; 后同步给主内存,但线程 1 不知道。

解决。

private static volatile boolean flag = true;

线程 2 将 flag = false; 后同步给主内存,volatile 缓存一致性协议会将其他线程的工作内存中的 flag 全部失效,让线程重新去主内存读取。

package com.geek.synchronize.geek.demo;

/**
 * 可见性。 ~ 一个线程对共享变量的修改,另一个线程不能立即得到最新值。
 *
 * @author geek
 */
public class TestVisibility {

    private static final Object OBJECT = new Object();
//    private static volatile boolean flag = true;
    /**
     * 1、创建一个共享变量。
     * 多个线程都会访问的数据,称为线程的共享数据。
     */
    private static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        // 2、创建一条线程不断读取共享变量。
        Thread thread = new Thread(() -> {
            while (flag) {
                synchronized (OBJECT) {

                }
            }
        });
        thread.start();

        Thread.sleep(2000);

        // 3、创建一条线程修改共享变量。
        Thread thread1 = new Thread(() -> {
            flag = false;
            System.out.println("时间到,线程 2 设置为 false。");
        });
        thread1.start();
    }

}

synchronized 会增加 Lock、Unlock 操作,让线程的工作内存重新获取主内存的最新值。

或者将 synchronized 改为 sout。

        Thread thread = new Thread(() -> {
            while (flag) {
//                synchronized (OBJECT) {
//                }
                System.out.println("flag = " + flag);
            }
        });

因为这个方法是加了 synchronized。

    /**
     * Prints a String and then terminate the line.  This method behaves as
     * though it invokes <code>{@link #print(String)}</code> and then
     * <code>{@link #println()}</code>.
     *
     * @param x  The <code>String</code> to be printed.
     */
    public void println(String x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

在这里插入图片描述



小结。

synchronized 保证可见性的原理。
执行 synchronized 时,会对应 lock 原子操作会刷新工作内存中共享变量的值。



synchronized 与有序性。
目标。

学习使用 synchronized 保证有序性的原理。

  • 为什么要重排序。
    为了提高程序的执行效率,编译器和 CPU 会对程序中代码进行重排序。

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

以下数据有依赖关系,不能重排序。

写后读。

int a = 1;
int b = a;

写后写。

int a = 1;
int a = 2;

读后写。

int a = 1;
int b = a;
int a = 2;

编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重排序。

int a = 1;
int b = a;
int c = a + b;

上面 3 个操作的数据依赖关系如图所示。

在这里插入图片描述

如上图所示 a 和 c 之间存在数据依赖关系,同时 b 和 c 之间也存在数据依赖关系。因此在最终执行的指令序列中,c 不能被重排序到 a 和 b 的前面。但 a 和 b 之间没有数据依赖关系,编译器和处理器可以重排序 a 和 b 之间的执行顺序。下图是该程序的两种执行顺序。

可以这样:
int a = 1;
int b = 2;
int c = a + b;
也可以重排序这样:
int b = 2;
int a = 1;
int c = a + b;


使用 synchronized 保证有序性。
package com.geek.synchronize.geek.demo;

import org.openjdk.jcstress.annotations.*;
import org.openjdk.jcstress.infra.results.I_Result;

/**
 * 有序性。
 *
 * @author geek
 */
@JCStressTest
@Outcome(id = {"1"}, expect = Expect.ACCEPTABLE, desc = "ok")
@Outcome(id = {"4"}, expect = Expect.ACCEPTABLE_INTERESTING, desc = "danger2")
@Outcome(id = "0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "danger")
@State
public class TestOrderliness02 {

   private final Object object = new Object();

   private Integer num = 0;
   private Boolean ready = false;

   /**
    * 线程 1 执行的代码。
    * // @Actor 多个线程执行该方法。
    *
    * @param iResult
    */
   @Actor
   public void actor1(I_Result iResult) {
      synchronized (object) {
         if (this.ready) {
            iResult.r1 = this.num + this.num;
         } else {
            iResult.r1 = 1;
         }
      }
   }

   /**
    * 线程 2 执行的代码。
    * // @Actor 多个线程执行该方法。
    *
    * @param iResult
    */
   @Actor
   public void actor2(I_Result iResult) {
      synchronized (object) {
         this.num = 2;
         this.ready = true;
      }
   }

}
package com.geek.synchronize.geek.demo;

import org.openjdk.jcstress.annotations.*;
import org.openjdk.jcstress.infra.results.I_Result;

/**
 * 有序性。
 *
 * @author geek
 */
@JCStressTest
@Outcome(id = {"1"}, expect = Expect.ACCEPTABLE, desc = "ok")
@Outcome(id = {"4"}, expect = Expect.ACCEPTABLE_INTERESTING, desc = "danger2")
@Outcome(id = "0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "danger")
@State
public class TestOrderliness02 {

   private final Object object = new Object();

   private Integer num = 0;
   private Boolean ready = false;

   /**
    * 线程 1 执行的代码。
    * // @Actor 多个线程执行该方法。
    *
    * @param iResult
    */
   @Actor
   public void actor1(I_Result iResult) {
      synchronized (object) {
         if (this.ready) {
            iResult.r1 = this.num + this.num;
         } else {
            iResult.r1 = 1;
         }
      }
   }

   /**
    * 线程 2 执行的代码。
    * // @Actor 多个线程执行该方法。
    *
    * @param iResult
    */
   @Actor
   public void actor2(I_Result iResult) {
      synchronized (object) {
         this.num = 2;
         this.ready = true;
      }
   }

}

G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\bin\java.exe "-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin" -Dfile.encoding=UTF-8 -classpath G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\jre\lib\charsets.jar;G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\jre\lib\deploy.jar;G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\jre\lib\ext\access-bridge-64.jar;G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\jre\lib\ext\cldrdata.jar;G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\jre\lib\ext\dnsns.jar;G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\jre\lib\ext\jaccess.jar;G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\jre\lib\ext\jfxrt.jar;G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\jre\lib\ext\localedata.jar;G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\jre\lib\ext\nashorn.jar;G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\jre\lib\ext\sunec.jar;G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\jre\lib\ext\sunjce_provider.jar;G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\jre\lib\ext\sunmscapi.jar;G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\jre\lib\ext\sunpkcs11.jar;G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\jre\lib\ext\zipfs.jar;G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\jre\lib\javaws.jar;G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\jre\lib\jce.jar;G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\jre\lib\jfr.jar;G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\jre\lib\jfxswt.jar;G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\jre\lib\jsse.jar;G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\jre\lib\management-agent.jar;G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\jre\lib\plugin.jar;G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\jre\lib\resources.jar;G:\lyfGeek\ProgramFiles\Java\jdk1.8.0_241\jre\lib\rt.jar;G:\lyfGeek\IdeaProjects\synchronized-geek\target\classes;G:\lyfGeek\maven_repository\org\openjdk\jcstress\jcstress-core\0.3\jcstress-core-0.3.jar;G:\lyfGeek\maven_repository\net\sf\jopt-simple\jopt-simple\4.6\jopt-simple-4.6.jar;G:\lyfGeek\maven_repository\org\springframework\boot\spring-boot-starter\2.7.9\spring-boot-starter-2.7.9.jar;G:\lyfGeek\maven_repository\org\springframework\boot\spring-boot\2.7.9\spring-boot-2.7.9.jar;G:\lyfGeek\maven_repository\org\springframework\spring-context\5.3.25\spring-context-5.3.25.jar;G:\lyfGeek\maven_repository\org\springframework\spring-aop\5.3.25\spring-aop-5.3.25.jar;G:\lyfGeek\maven_repository\org\springframework\spring-beans\5.3.25\spring-beans-5.3.25.jar;G:\lyfGeek\maven_repository\org\springframework\spring-expression\5.3.25\spring-expression-5.3.25.jar;G:\lyfGeek\maven_repository\org\springframework\boot\spring-boot-autoconfigure\2.7.9\spring-boot-autoconfigure-2.7.9.jar;G:\lyfGeek\maven_repository\org\springframework\boot\spring-boot-starter-logging\2.7.9\spring-boot-starter-logging-2.7.9.jar;G:\lyfGeek\maven_repository\ch\qos\logback\logback-classic\1.2.11\logback-classic-1.2.11.jar;G:\lyfGeek\maven_repository\ch\qos\logback\logback-core\1.2.11\logback-core-1.2.11.jar;G:\lyfGeek\maven_repository\org\apache\logging\log4j\log4j-to-slf4j\2.17.2\log4j-to-slf4j-2.17.2.jar;G:\lyfGeek\maven_repository\org\apache\logging\log4j\log4j-api\2.17.2\log4j-api-2.17.2.jar;G:\lyfGeek\maven_repository\org\slf4j\jul-to-slf4j\1.7.36\jul-to-slf4j-1.7.36.jar;G:\lyfGeek\maven_repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;G:\lyfGeek\maven_repository\org\springframework\spring-core\5.3.25\spring-core-5.3.25.jar;G:\lyfGeek\maven_repository\org\springframework\spring-jcl\5.3.25\spring-jcl-5.3.25.jar;G:\lyfGeek\maven_repository\org\yaml\snakeyaml\1.30\snakeyaml-1.30.jar;G:\lyfGeek\maven_repository\org\projectlombok\lombok\1.18.26\lombok-1.18.26.jar;G:\lyfGeek\maven_repository\org\slf4j\slf4j-api\1.7.36\slf4j-api-1.7.36.jar org.openjdk.jcstress.Main -t com.geek.synchronize.geek.demo
Java Concurrency Stress Tests
---------------------------------------------------------------------------------
Rev: 081b1492fcc0, built by shade with 9-ea at 2017-03-29T11:30:22Z

Probing what VM modes are available:
 (failures are non-fatal, but may miss some interesting cases)

----- [OK] [-Xint]
----- [OK] [-XX:TieredStopAtLevel=1]
----- [OK] []
----- [N/A] [-XX:+UnlockDiagnosticVMOptions, -XX:+StressLCM, -XX:+StressGCM]
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
Error: VM option 'StressLCM' is develop and is available only in debug version of VM.


----- [OK] [-XX:-TieredCompilation]
----- [N/A] [-XX:-TieredCompilation, -XX:+UnlockDiagnosticVMOptions, -XX:+StressLCM, -XX:+StressGCM]
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
Error: VM option 'StressLCM' is develop and is available only in debug version of VM.



Initializing and probing the target VM: 
 (all failures are non-fatal, but may affect testing accuracy)

----- [OK] Unlocking diagnostic VM options
----- [OK] Trimming down the number of compiler threads
----- [OK] Trimming down the number of parallel GC threads
----- [OK] Trimming down the number of concurrent GC threads
----- [OK] Trimming down the number of G1 concurrent refinement GC threads
----- [FAILED] Testing @Contended works on all results and infra objects
Exception in thread "main" java.lang.IllegalStateException: G:.lyfGeek.IdeaProjects.synchronized-geek.target.classes.com.geek.synchronize.geek.SynchronizedGeekApplication
	at org.openjdk.jcstress.util.Reflections.getClasses(Reflections.java:66)
	at org.openjdk.jcstress.vm.ContendedTestMain.main(ContendedTestMain.java:43)
Caused by: java.lang.ClassNotFoundException: G:.lyfGeek.IdeaProjects.synchronized-geek.target.classes.com.geek.synchronize.geek.SynchronizedGeekApplication
	at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:348)
	at org.openjdk.jcstress.util.Reflections.getClasses(Reflections.java:64)
	... 1 more

----- [OK] Unlocking Whitebox API for online de-optimization
----- [OK] Testing allocation profiling
----- [FAILED] Trying Thread.onSpinWait
Exception in thread "main" java.lang.NoSuchMethodError: java.lang.Thread.onSpinWait()V
	at org.openjdk.jcstress.vm.ThreadSpinWaitTestMain.main(ThreadSpinWaitTestMain.java:30)


Burning up to figure out the exact CPU count....... done!

  Hardware threads in use/available: 8/8, no yielding.
  Test preset mode: "default"
  Writing the test results to "jcstress-results-2023-02-27-17-18-04.bin.gz"
  Parsing results to "results/"
  Running each test matching "com.geek.synchronize.geek.demo" for 1 forks, 5 iterations, 1000 ms each
  Each JVM would execute at most 5 tests in the row.
  Solo stride size will be autobalanced within [10, 10000] elements, but taking no more than 100 Mb.

      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (fork: #1, iteration #1, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8, -XX:TieredStopAtLevel=1])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1     3,790,783               ACCEPTABLE  ok                                                          
               4     6,835,547   ACCEPTABLE_INTERESTING  danger2                                                     

      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (fork: #1, iteration #1, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8, -XX:-TieredCompilation])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1     5,301,952               ACCEPTABLE  ok                                                          
               4     4,851,588   ACCEPTABLE_INTERESTING  danger2                                                     

      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (fork: #1, iteration #1, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1     5,130,434               ACCEPTABLE  ok                                                          
               4     5,288,666   ACCEPTABLE_INTERESTING  danger2                                                     

      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (fork: #1, iteration #1, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8, -Xint])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1       614,473               ACCEPTABLE  ok                                                          
               4       422,727   ACCEPTABLE_INTERESTING  danger2                                                     

      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (fork: #1, iteration #2, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8, -XX:TieredStopAtLevel=1])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1     4,467,865               ACCEPTABLE  ok                                                          
               4     4,017,385   ACCEPTABLE_INTERESTING  danger2                                                     

      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (fork: #1, iteration #2, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8, -XX:-TieredCompilation])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1     5,546,425               ACCEPTABLE  ok                                                          
               4     3,673,025   ACCEPTABLE_INTERESTING  danger2                                                     

      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (fork: #1, iteration #2, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1     4,734,553               ACCEPTABLE  ok                                                          
               4     6,176,647   ACCEPTABLE_INTERESTING  danger2                                                     

      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (fork: #1, iteration #2, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8, -Xint])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1       943,127               ACCEPTABLE  ok                                                          
               4       357,103   ACCEPTABLE_INTERESTING  danger2                                                     

      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (fork: #1, iteration #3, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8, -XX:TieredStopAtLevel=1])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1     4,700,717               ACCEPTABLE  ok                                                          
               4     6,405,093   ACCEPTABLE_INTERESTING  danger2                                                     

      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (fork: #1, iteration #3, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8, -XX:-TieredCompilation])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1     7,733,660               ACCEPTABLE  ok                                                          
               4     4,667,210   ACCEPTABLE_INTERESTING  danger2                                                     

      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (fork: #1, iteration #3, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1     6,779,372               ACCEPTABLE  ok                                                          
               4     6,015,368   ACCEPTABLE_INTERESTING  danger2                                                     

      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (fork: #1, iteration #3, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8, -Xint])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1       815,943               ACCEPTABLE  ok                                                          
               4       433,777   ACCEPTABLE_INTERESTING  danger2                                                     

      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (fork: #1, iteration #4, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8, -XX:TieredStopAtLevel=1])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1     4,275,341               ACCEPTABLE  ok                                                          
               4     6,096,179   ACCEPTABLE_INTERESTING  danger2                                                     

      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (fork: #1, iteration #4, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8, -XX:-TieredCompilation])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1     5,240,526               ACCEPTABLE  ok                                                          
               4     7,022,804   ACCEPTABLE_INTERESTING  danger2                                                     

      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (fork: #1, iteration #4, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1     7,164,457               ACCEPTABLE  ok                                                          
               4     6,310,513   ACCEPTABLE_INTERESTING  danger2                                                     

      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (fork: #1, iteration #4, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8, -Xint])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1       797,507               ACCEPTABLE  ok                                                          
               4       450,373   ACCEPTABLE_INTERESTING  danger2                                                     

      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (fork: #1, iteration #5, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8, -XX:TieredStopAtLevel=1])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1     7,152,402               ACCEPTABLE  ok                                                          
               4     4,894,088   ACCEPTABLE_INTERESTING  danger2                                                     

      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (fork: #1, iteration #5, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8, -XX:-TieredCompilation])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1     6,872,983               ACCEPTABLE  ok                                                          
               4     6,619,687   ACCEPTABLE_INTERESTING  danger2                                                     

      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (fork: #1, iteration #5, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1     7,457,618               ACCEPTABLE  ok                                                          
               4     6,199,182   ACCEPTABLE_INTERESTING  danger2                                                     

      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (fork: #1, iteration #5, JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8, -Xint])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1       938,240               ACCEPTABLE  ok                                                          
               4       508,110   ACCEPTABLE_INTERESTING  danger2                                                     

(ETA:        now) (Rate: 4.19E+07 samples/sec) (Tests: 1 of 1) (Forks:  4 of 4) (Iterations: 20 of 20; 20 passed, 0 failed, 0 soft errs, 0 hard errs) 

RUN COMPLETE.

RUN RESULTS:
------------------------------------------------------------------------------------------------------------------------

*** INTERESTING tests
  Some interesting behaviors observed. This is for the plain curiosity.

  4 matching test results. 
      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8, -XX:-TieredCompilation])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1    30,695,546               ACCEPTABLE  ok                                                          
               4    26,834,314   ACCEPTABLE_INTERESTING  danger2                                                     

      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8, -XX:TieredStopAtLevel=1])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1    24,387,108               ACCEPTABLE  ok                                                          
               4    28,248,292   ACCEPTABLE_INTERESTING  danger2                                                     

      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8, -Xint])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1     4,109,290               ACCEPTABLE  ok                                                          
               4     2,172,090   ACCEPTABLE_INTERESTING  danger2                                                     

      [OK] com.geek.synchronize.geek.demo.TestOrderliness02
    (JVM args: [-javaagent:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=54945:G:\Program Files\JetBrains\IntelliJ IDEA 2022.2.1\bin, -Dfile.encoding=UTF-8])
  Observed state   Occurrences              Expectation  Interpretation                                              
               0             0   ACCEPTABLE_INTERESTING  danger                                                      
               1    31,266,434               ACCEPTABLE  ok                                                          
               4    29,990,376   ACCEPTABLE_INTERESTING  danger2                                                     


*** FAILED tests
  Strong asserts were violated. Correct implementations should have no assert failures here.

  0 matching test results. 

*** ERROR tests
  Tests break for some reason, other than failing the assert. Correct implementations should have none.

  0 matching test results. 

*** All remaining tests
  Tests that do not fall into any of the previous categories.

  0 matching test results.  Use -v to print them.

------------------------------------------------------------------------------------------------------------------------

HTML report was generated. Look at results/index.html for the complete run results.

Will throw any pending exceptions at this point.
Done.

Process finished with exit code 0

只出现了 1 和 4。



synchronized 保证有序性的原理。

synchronized 后,虽然进行了重排序,保证只有一个线程会进入同步代码块,也能保证有序性。



小结。

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



第四章:synchronized 的特性。

可重入特性。
目标。

了解什么是可重入。
了解可重入的原理。



什么是可重入。

一个线程可以多次执行 synchronized,重复获取同一把锁。

package com.geek.synchronize.geek.demo;

/**
 * 演示 synchronized 可重入。
 * - 自定义一个线程类。
 * - 在线程类的 run(); 方法中使用嵌套的同步代码快。
 * - 使用两个线程执行。
 *
 * @author geek
 */
public class ReentrantDemo {

    public static void main(String[] args) {
        new MyThread().start();
        new MyThread().start();
    }

}

/**
 * 自定义一个线程类。
 */
class MyThread extends Thread {

    /**
     * If this thread was constructed using a separate
     * <code>Runnable</code> run object, then that
     * <code>Runnable</code> object's <code>run</code> method is called;
     * otherwise, this method does nothing and returns.
     * <p>
     * Subclasses of <code>Thread</code> should override this method.
     *
     * @see #start()
     * @see #stop()
     * @see Thread(ThreadGroup, Runnable, String)
     */
    @Override
    public void run() {
//        super.run();
        synchronized (MyThread.class) {
            System.out.println(getName() + " ~ 进入了同步代码块 1。");

            synchronized (MyThread.class) {
                System.out.println(getName() + " ~ 进入了同步代码块 2。");
            }

        }
    }

}

/*
Connected to the target VM, address: '127.0.0.1:56283', transport: 'socket'
Thread-0 ~ 进入了同步代码块 1。
Thread-0 ~ 进入了同步代码块 2。
Thread-1 ~ 进入了同步代码块 1。
Thread-1 ~ 进入了同步代码块 2。
Disconnected from the target VM, address: '127.0.0.1:56283', transport: 'socket'

Process finished with exit code 0
 */



可重入原理。

synchronized 的锁对象中有一个计数器(recursions 变量)会记录线程获得几次锁。



可重入的好处。
  • 可以避免死锁。

  • 可以让我们更好的来封装代码。



小结。

synchronized 是可重入锁,内部锁对象中会有一个计数器记录线程获取了几次锁,在执行完同步代码块时,计数器的数量会 -1,直到计数器的数量为 0,就释放这个锁。



不可中断特性。
目标。

学习 synchronized 不可中断特性。
学习 Lock 的可中断特性。



什么是不可中断。

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



synchronized 不可中断演示。

指的是同一个线程获得锁之后,可以直接再次获取该锁。

synchronized 是不可中断,处于阻塞状态的线程会一直等待锁。

package com.geek.synchronize.geek.demo;

/**
 * 演示 synchronized 不可中断。
 * - 定义一个 Runnable。
 * - 在 Runnable 定义同步代码块。
 * - 先开启一个线程来执行同步代码块,保证不退出同步代码块。
 * - 后开启一个线程来执行同步代码块(阻塞状态)。
 * - 停止第二个线程。
 *
 * @author geek
 */
public class SynchronizedNonInterrupt {

    private static final Object OBJECT = new Object();

    public static void main(String[] args) {

        // - 定义一个 Runnable。
        Runnable runnable = () -> {
            // - 在 Runnable 定义同步代码块。
            synchronized (OBJECT) {
                System.out.println(Thread.currentThread().getName() + " 进入同步代码块。");
                // 保证不退出同步代码块。
                try {
                    Thread.sleep(99999);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        // - 先开启一个线程来执行同步代码块,保证不退出同步代码块。
        Thread thread = new Thread(runnable);
        thread.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        // - 后开启一个线程来执行同步代码块(阻塞状态)。
        Thread thread1 = new Thread(runnable);
        thread1.start();
        // - 停止第二个线程。
        System.out.println("停止线程前。");
        thread1.interrupt();
        System.out.println("停止线程后。");

        System.out.println(thread.getState());
        System.out.println(thread1.getState());
    }

}

/*
Connected to the target VM, address: '127.0.0.1:57079', transport: 'socket'
Thread-0 进入同步代码块。
停止线程前。
停止线程后。
TIMED_WAITING
BLOCKED
 */

/*
Disconnected from the target VM, address: '127.0.0.1:57079', transport: 'socket'

Process finished with exit code 130
 */



ReentrantLock 可中断演示。
package com.geek.synchronize.geek.demo;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * ReentrantLock 不可中断 & 可中断。
 *
 * @author geek
 */
public class ReentrantLockDemo {

    private static final Lock LOCK = new ReentrantLock();

    public static void main(String[] args) {
//        testReentrantLockInterrupt();
        testReentrantLockInterruptTryLock();
    }

    /**
     * 演示 Lock 不可中断。lock.tryLock();
     */
    private static void testReentrantLockInterruptTryLock() {

        Runnable runnable = () -> {
            String name = Thread.currentThread().getName();
            boolean tryLock = false;
            try {
                tryLock = LOCK.tryLock(3, TimeUnit.SECONDS);
                if (tryLock) {
                    System.out.println(name + " 获得锁,进入锁执行。");
                    Thread.sleep(99999);
                } else {
                    System.out.println(name + " 没有在指定时间内获得锁。");
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                System.out.println("tryLock = " + tryLock);
                if (tryLock) {
                    LOCK.unlock();
                    System.out.println(name + " 释放锁。");
                }
            }
        };

        Thread thread1 = new Thread(runnable);
        thread1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        Thread thread2 = new Thread(runnable);
        thread2.start();
//
//        System.out.println("停止 thread2 前。");
//        thread2.interrupt();
//        System.out.println("停止 thread2 前。");
//        System.out.println("thread1.getState() = " + thread1.getState());
//        System.out.println("thread2.getState() = " + thread2.getState());
    }
/*
Connected to the target VM, address: '127.0.0.1:65386', transport: 'socket'
Thread-0 获得锁,进入锁执行。
Thread-1 没有在指定时间内获得锁。
tryLock = false
 */
/*
Connected to the target VM, address: '127.0.0.1:65386', transport: 'socket'
Thread-0 获得锁,进入锁执行。
Thread-1 没有在指定时间内获得锁。
tryLock = false
tryLock = true
Thread-0 释放锁。
Disconnected from the target VM, address: '127.0.0.1:65386', transport: 'socket'

Process finished with exit code 0
 */

    /**
     * 演示 Lock 不可中断。lock.lock();
     */
    private static void testReentrantLockInterrupt() {

        Runnable runnable = () -> {
            String name = Thread.currentThread().getName();
            try {
                LOCK.lock();
                System.out.println(name + " 获得锁,进入锁执行。");
                Thread.sleep(99999);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                LOCK.unlock();
                System.out.println(name + " 释放锁。");
            }
        };

        Thread thread1 = new Thread(runnable);
        thread1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        Thread thread2 = new Thread(runnable);
        thread2.start();

        System.out.println("停止 thread2 前。");
        thread2.interrupt();
        System.out.println("停止 thread2 前。");
        System.out.println("thread1.getState() = " + thread1.getState());
        System.out.println("thread2.getState() = " + thread2.getState());
    }

/*
Connected to the target VM, address: '127.0.0.1:65046', transport: 'socket'
Thread-0 获得锁,进入锁执行。
停止 thread2 前。
停止 thread2 前。
thread1.getState() = TIMED_WAITING
thread2.getState() = RUNNABLE
Thread-0 释放锁。
Thread-1 获得锁,进入锁执行。
Thread-1 释放锁。
Exception in thread "Thread-1" java.lang.RuntimeException: java.lang.InterruptedException: sleep interrupted
	at com.geek.synchronize.geek.demo.ReentrantLockDemo.lambda$testReentrantLockInterrupt$0(ReentrantLockDemo.java:31)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.geek.synchronize.geek.demo.ReentrantLockDemo.lambda$testReentrantLockInterrupt$0(ReentrantLockDemo.java:29)
	... 1 more
Disconnected from the target VM, address: '127.0.0.1:65046', transport: 'socket'

Process finished with exit code 0
 */

}



小结。

不可中断是指,当一个线程获得锁后,另一个线程一直处于阻塞或等待状态,前一个线程不释放锁,后一个线程会一直阻塞或等待,不可被中断。

synchronized 不可被中断。
Lock 的 lock(); 方法是不可中断的。
Lock 的 tryLock(); 方法是可中断的。



第五章:synchronized 原理。

javap 反汇编。
目标。

通过 javap 反汇编学习 synchronized 的原理。

我们编写一个简单的 synchronized 代码,如下。

package com.geek.synchronize.geek.demo;

/**
 * @author geek
 */
public class JavapDemo {

    private static final Object OBJECT = new Object();

    public static void main(String[] args) {
        synchronized (OBJECT) {
            System.out.println("1");
        }
    }

    public synchronized void test() {
        System.out.println("a");
    }

}

我们要看 synchronized 的原理,但是 synchronized 是一个关键字,看不到源码。我们可以将 class 文件进行反汇编。

JDK 自带的一个工具:javap,对字节码进行反汇编,查看字节码指令。

PS G:\lyfGeek\IdeaProjects\synchronized-geek\target\classes\com\geek\synchronize\geek\demo> javap -p -v -c .\JavapDemo.class
Classfile /G:/lyfGeek/IdeaProjects/synchronized-geek/target/classes/com/geek/synchronize/geek/demo/JavapDemo.class
  Last modified Feb 27, 2023; size 917 bytes
  MD5 checksum 96051b848234ccde1ec75fcda847a294
  Compiled from "JavapDemo.java"               
public class com.geek.synchronize.geek.demo.JavapDemo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#31         // java/lang/Object."<init>":()V
   #2 = Fieldref           #8.#32         // com/geek/synchronize/geek/demo/JavapDemo.OBJECT:Ljava/lang/Object;
   #3 = Fieldref           #33.#34        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = String             #35            // 1
   #5 = Methodref          #36.#37        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #6 = String             #38            // a
   #7 = Class              #39            // java/lang/Object
   #8 = Class              #40            // com/geek/synchronize/geek/demo/JavapDemo
   #9 = Utf8               OBJECT
  #10 = Utf8               Ljava/lang/Object;
  #11 = Utf8               <init>
  #12 = Utf8               ()V
  #13 = Utf8               Code
  #14 = Utf8               LineNumberTable
  #15 = Utf8               LocalVariableTable
  #16 = Utf8               this
  #17 = Utf8               Lcom/geek/synchronize/geek/demo/JavapDemo;
  #18 = Utf8               main
  #19 = Utf8               ([Ljava/lang/String;)V
  #20 = Utf8               args
  #21 = Utf8               [Ljava/lang/String;
  #22 = Utf8               StackMapTable
  #23 = Class              #21            // "[Ljava/lang/String;"
  #24 = Class              #39            // java/lang/Object
  #25 = Class              #41            // java/lang/Throwable
  #26 = Utf8               MethodParameters
  #27 = Utf8               test
  #28 = Utf8               <clinit>
  #29 = Utf8               SourceFile
  #30 = Utf8               JavapDemo.java
  #31 = NameAndType        #11:#12        // "<init>":()V
  #32 = NameAndType        #9:#10         // OBJECT:Ljava/lang/Object;
  #33 = Class              #42            // java/lang/System
  #34 = NameAndType        #43:#44        // out:Ljava/io/PrintStream;
  #35 = Utf8               1
  #36 = Class              #45            // java/io/PrintStream
  #37 = NameAndType        #46:#47        // println:(Ljava/lang/String;)V
  #38 = Utf8               a
  #39 = Utf8               java/lang/Object
  #40 = Utf8               com/geek/synchronize/geek/demo/JavapDemo
  #41 = Utf8               java/lang/Throwable
  #42 = Utf8               java/lang/System
  #43 = Utf8               out
  #44 = Utf8               Ljava/io/PrintStream;
  #45 = Utf8               java/io/PrintStream
  #46 = Utf8               println
  #47 = Utf8               (Ljava/lang/String;)V
{
  private static final java.lang.Object OBJECT;
    descriptor: Ljava/lang/Object;
    flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL

  public com.geek.synchronize.geek.demo.JavapDemo();
    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   Lcom/geek/synchronize/geek/demo/JavapDemo;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: getstatic     #2                  // Field OBJECT:Ljava/lang/Object;
         3: dup
         4: astore_1
         5: monitorenter
         6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         9: ldc           #4                  // String 1
        11: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        14: aload_1
        15: monitorexit
        16: goto          24
        19: astore_2
        20: aload_1
        21: monitorexit
        22: aload_2
        23: athrow
        24: return
      Exception table:
         from    to  target type
             6    16    19   any
            19    22    19   any
      LineNumberTable:
        line 11: 0
        line 12: 6
        line 13: 14
        line 14: 24
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      25     0  args   [Ljava/lang/String;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 19
          locals = [ class "[Ljava/lang/String;", class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4
    MethodParameters:
      Name                           Flags
      args

  public synchronized void test();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #6                  // String a
         5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 17: 0
        line 18: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcom/geek/synchronize/geek/demo/JavapDemo;

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: new           #7                  // class java/lang/Object
         3: dup
         4: invokespecial #1                  // Method java/lang/Object."<init>":()V
         7: putstatic     #2                  // Field OBJECT:Ljava/lang/Object;
        10: return
      LineNumberTable:
        line 8: 0
}
SourceFile: "JavapDemo.java"

在这里插入图片描述



monitorenter。

首先我们来看一下 JVM 规范中对于 monitorenter 的描述。

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.monitorenter

Description
The objectref must be of type reference.

Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:

If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.

If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.

If another thread already owns the monitor associated with objectref, the thread blocks until the monitor’s entry count is zero, then tries again to gain ownership.

翻译过来。
每一个对象都会和一个监视器 monitor 关联。监视器被占用时会被锁住,其他线程无法来获取该 monitor。当 JVM 执行某个线程的某个方法内部的 monitorenter 时,它会尝试去获取当前对象对应的 monitor 的所有权。其过程如下。

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


monitorenter 小结。

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



monitorexit。

首先我们来看一下 JVM 规范中对于 monitorexit 的描述。

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.monitorexit

The objectref must be of type reference.

The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.

The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

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

monitorexit 释放锁。

monitorexit 插入在方法结束处和异常处,JVM 保证每个 monitorenter 必须有对应的 monitorexit。

面试题 synchroznied 出现异常会释放锁吗?

会释放锁。



同步方法。

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.11.10

可以看到同步方法在反汇编后,会增加 ACC_SYNCHRONIZED 修饰。会隐式调用 monitorenter 和 monitorexit。在执行同步方法前会调用 monitorenter,在执行完同步方法后会调用 monitorexit。



小结。

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

面试题:synchronized 与 Lock 的区别。

synchronized 是关键字,而 Lock 是一个接口。
synchronized 会自动释放锁,而 Lock 必须手动释放锁。
synchronized 是不可中断的,Lock 可以中断也可以不中断。
通过 Lock 可以知道线程有没有拿到锁,而 synchronized 不能。
synchronized 能锁住方法和代码块,而 Lock 只能锁住代码块。
Lock 可以使用读锁提高多线程读效率。
synchronized 是非公平锁,ReentrantLock 可以控制是否是公平锁。



深入 JVM 源码。

目标。

通过 JVM 源码分析 synchronized 的原理。

JVM 源码下载。

http://openjdk.java.net/ --> Mercurial --> jdk8 --> hotspot --> zip

IDE(Clion)下载。

https://www.jetbrains.com/



monitor 监视器锁。

可以看出无论是 synchronized 代码块还是 synchronized 方法,其线程安全的语义实现最终依赖一个叫 monitor 的东西,那么这个神秘的东西是什么呢?下面让我们来详细介绍一下。

在 HotSpot 虚拟机中,monitor 是由 ObjectMonitor 实现的。其源码是用 C++ 来实现的,位于 HotSpot 虚拟机源码 ObjectMonitor.hpp 文件中。(src/share/vm/runtime/objectMonitor.hpp)。

ObjectMonitor 主要数据结构如下。

ObjectMonitor() {
    _header = NULL;
    _count = 0;
    _waiters = 0,
    _recursions = 0;  // 线程的重入次数。
    _object = NULL;  // 存储该 monitor 的对象。
    _owner = NULL;  // 标识拥有该 monitor 的线程。
    _WaitSet = NULL;  // 处于 wait 状态的线程,会被加入到 _WaitSet。
    _WaitSetLock = 0 ;
    _Responsible = NULL;
    _succ = NULL;
    _cxq = NULL;  // 多线程竞争锁时的单向列表。
    FreeNext = NULL;
    _EntryList = NULL;  // 处于等待锁 block 状态的线程,会被加入到该列表。
    _SpinFreq = 0;
    _SpinClock = 0;
    OwnerIsThread = 0;
}
  • _owner:初始时为NULL。当有线程占有该 monitor 时,owner 标记为该线程的唯一标识。当线程释放 monitor 时,owner 又恢复为 NULL。owner 是一个临界资源,JVM 是通过 CAS 操作来保证其线程安全的。

  • -_cxq:竞争队列,所有请求锁的线程首先会被放在这个队列中(单向链接)。_cxq 是一个临界资源,JVM 通过 CAS 原子指令来修改 _cxq 队列。修改前 _cxq 的旧值填入了 node 的 next 字段,_cxq 指向新值(新线程)。因此 _cxq 是一个后进先出的 stack(栈)。

  • _EntryList:_cxq 队列中有资格成为候选资源的线程会被移动到该队列中。

  • _WaitSet:因为调用 wait(); 方法而被阻塞的线程会被放在该队列中。

每一个 Java 对象都可以与一个监视器 monitor 关联,我们可以把它理解成为一把锁,当一个线程想要执行一段被 synchronized 圈起来的同步方法或者代码块时,该线程得先获取到 synchronized 修饰的对象对应的 monitor。

我们的 Java 代码里不会显示地去创造这么一个 monitor 对象,我们也无需创建,事实上可以这么理解:monitor 并不是随着对象创建而创建的。我们是通过 synchronized 修饰符告诉 JVM 需要为我们的某个对象创建关联的 monitor 对象。每个线程都存在两个 ObjectMonitor 对象列表,分别为 free 和 used 列表。同时 JVM 中也维护着 global locklist。当线程需要 ObjectMonitor 对象时,首先从线程自身的 free 表中申请,若存在则使用,若不存在则从 global list 中申请。

ObjectMonitor 的数据结构中包含:_owner、_WaitSet 和 _EntryList,它们之间的关系转换可以用下图表示。

在这里插入图片描述



monitor 竞争。
  • 执行 monitorenter 时,会调用 InterpreterRuntime.cpp(位于:src/share/vm/interpreter/interpreterRuntime.cpp)的 InterpreterRuntime::monitorenter 函数。具体代码可参见 HotSpot 源码。

  • 对于重量级锁,monitorenter 函数中会调用 ObjectSynchronizer::slow_enter

  • 最终调用 ObjectMonitor::enter(位于:src/share/vm/runtime/objectMonitor.cpp),源码如下。

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread,
BasicObjectLock* elem))
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
if (PrintBiasedLockingStatistics) {
Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
}
Handle h_obj(thread, elem->obj());
assert(Universe::heap()->is_in_reserved_or_null(h_obj())"must be NULL or an object");
if (UseBiasedLocking) {
// Retry fast entry if bias is revoked to avoid unnecessary inflation
ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
} else {
ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
}
assert(Universe::heap()->is_in_reserved_or_null(elem->obj())"must be NULL or an object");
  • 对于重量级锁,monitorenter 函数中会调用 ObjectSynchronizer::slow_enter。

  • 最终调用 ObjectMonitor::enter(位于:src/share/vm/runtime/objectMonitor.cpp),源码如下。

void ATTR ObjectMonitor::enter(TRAPS) {
    // The following code is ordered to check the most common cases first
    // and to reduce RTS->RTO cache line upgrades on SPARC and IA32 processors.
    Thread * const Self = THREAD ;
    void * cur ;
    // 通过CAS操作尝试把monitor的_owner字段设置为当前线程
    cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
    if (cur == NULL) {
    // Either ASSERT _recursions == 0 or explicitly set _recursions = 0.
    assert (_recursions == 0"invariant") ;
    assert (_owner == Self, "invariant") ;
    // CONSIDER: set or assert OwnerIsThread == 1
    return ;
    }
    // 线程重入,recursions++
    if (cur == Self) {
    // TODO-FIXME: check for integer overflow! BUGID 6557169.
    _recursions ++ ;
    return ;
    }
    // 如果当前线程是第一次进入该monitor,设置_recursions为1,_owner为当前线程。
    if (Self->is_lock_owned ((address)cur)) {
assert (_recursions == 0"internal state error");
_recursions = 1 ;
// Commute owner from a thread-specific on-stack BasicLockObject address to
// a full-fledged "Thread *".
_owner = Self ;
OwnerIsThread = 1 ;
return ;
}
// 省略一些代码
for (;;) {
jt->set_suspend_equivalent();
// cleared by handle_special_suspend_equivalent_condition()
// or java_suspend_self()
// 如果获取锁失败,则等待锁的释放;
EnterI (THREAD) ;
if (!ExitSuspendEquivalent(jt)) break ;
//
// We have acquired the contended monitor, but while we were
// waiting another thread suspended us. We don't want to enter
// the monitor while suspended because that would surprise the
// thread that suspended us.
//
_recursions = 0 ;
_succ = NULL ;
exit (false, Self) ;
jt->java_suspend_self();
}
Self->set_current_pending_monitor(NULL);
}

此处省略锁的自旋优化等操作,统一放在后面 synchronized 优化中说。

以上代码的具体流程概括如下。

  1. 通过 CAS 尝试把 monitor 的 owner 字段设置为当前线程。
  2. 如果设置之前的 owner 指向当前线程,说明当前线程再次进入 monitor,即重入锁,执行 recursions++ ,记录重入的次数。
  3. 如果当前线程是第一次进入该 monitor,设置 recursions 为 1,_owner 为当前线程,该线程成功获得锁并返回。
  4. 如果获取锁失败,则等待锁的释放。


monitor 等待。

竞争失败等待调用的是 ObjectMonitor 对象的 EnterI 方法(位于:src/share/vm/runtime/objectMonitor.cpp),源码如下所示。

void ATTR ObjectMonitor::EnterI (TRAPS) {
    Thread * Self = THREAD ;
// Try the lock - TATAS
if (TryLock (Self) > 0) {
assert (_succ != Self , "invariant") ;
assert (_owner == Self , "invariant") ;
assert (_Responsible != Self , "invariant") ;
return ;
}
if (TrySpin (Self) > 0) {
assert (_owner == Self , "invariant") ;
assert (_succ != Self , "invariant") ;
assert (_Responsible != Self , "invariant") ;
return ;
}
// 省略部分代码
// 当前线程被封装成ObjectWaiter对象node,状态设置成ObjectWaiter::TS_CXQ;
ObjectWaiter node(Self) ;
Self->_ParkEvent->reset() ;
node._prev = (ObjectWaiter *) 0xBAD ;
node.TState = ObjectWaiter::TS_CXQ ;
// 通过CAS把node节点push到_cxq列表中
ObjectWaiter * nxt ;
for (;;) {
node._next = nxt = _cxq ;
if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;
// Interference - the CAS failed because _cxq changed. Just retry.
// As an optional optimization we retry the lock.
if (TryLock (Self) > 0) {
assert (_succ != Self , "invariant") ;
assert (_owner == Self , "invariant") ;
assert (_Responsible != Self , "invariant") ;
return ;
}
}
// 省略部分代码
for (;;) {
// 线程在被挂起前做一下挣扎,看能不能获取到锁
if (TryLock (Self) > 0) break ;
assert (_owner != Self, "invariant") ;
if ((SyncFlags & 2) && _Responsible == NULL) {
Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
}
// park self
if (_Responsible == Self || (SyncFlags & 1)) {
TEVENT (Inflated enter - park TIMED) ;
Self->_ParkEvent->park ((jlong) RecheckInterval) ;
// Increase the RecheckInterval, but clamp the value.
RecheckInterval *= 8 ;
if (RecheckInterval > 1000) RecheckInterval = 1000 ;
} else {
TEVENT (Inflated enter - park UNTIMED) ;
// 通过park将当前线程挂起,等待被唤醒
Self->_ParkEvent->park() ;
}
if (TryLock(Self) > 0) break ;
// 省略部分代码
}
// 省略部分代码
}

当该线程被唤醒时,会从挂起的点继续执行,通过 ObjectMonitor::TryLock 尝试获取锁,TryLock 方法实现如下。

int ObjectMonitor::TryLock (Thread * Self) {
for (;;) {
void * own = _owner ;
if (own != NULL) return 0 ;
if (Atomic::cmpxchg_ptr (Self, &_owner, NULL) == NULL) {
// Either guarantee _recursions == 0 or set _recursions = 0.
assert (_recursions == 0"invariant") ;
assert (_owner == Self, "invariant") ;
// CONSIDER: set or assert that OwnerIsThread == 1
return 1 ;
}
// The lock had been free momentarily, but we lost the race to the lock.
// Interference -- the CAS failed.
// We can either return -1 or retry.
// Retry doesn't make as much sense because the lock was just acquired.
if (true) return -1 ;
}
}

以上代码的具体流程概括如下。

  • 当前线程被封装成 ObjectWaiter 对象 node,状态设置成 ObjectWaiter::TS_CXQ。

  • 在 for 循环中,通过 CAS 把 node 节点 push 到 _cxq 列表中,同一时刻可能有多个线程把自己的 node 节点 push 到 _cxq 列表中。

  • node 节点 push 到 _cxq 列表之后,通过自旋尝试获取锁,如果还是没有获取到锁,则通过 park 将当前线程挂起,等待被唤醒。

  • 当该线程被唤醒时,会从挂起的点继续执行,通过 ObjectMonitor::TryLock 尝试获取锁。



monitor 释放。

当某个持有锁的线程执行完同步代码块时,会进行锁的释放,给其它线程机会执行同步代码,在 HotSpot 中,通过退出 monitor 的方式实现锁的释放,并通知被阻塞的线程,具体实现位于 ObjectMonitor 的 exit 方法中。(位于:src/share/vm/runtime/objectMonitor.cpp),源码如下所示。

void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {
Thread * Self = THREAD ;
// 省略部分代码
if (_recursions != 0) {
_recursions--; // this is simple recursive enter
TEVENT (Inflated exit - recursive) ;
return ;
}
// 省略部分代码
ObjectWaiter * w = NULL ;
int QMode = Knob_QMode ;
// qmode = 2:直接绕过EntryList队列,从cxq队列中获取线程用于竞争锁
if (QMode == 2 && _cxq != NULL) {
w = _cxq ;
assert (w != NULL"invariant") ;
assert (w->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
ExitEpilog (Self, w) ;
return ;
}
// qmode =3:cxq队列插入EntryList尾部;
if (QMode == 3 && _cxq != NULL) {
w = _cxq ;
for (;;) {
assert (w != NULL"Invariant") ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL&_cxq, w) ;
if (u == w) break ;
w = u ;
}
assert (w != NULL"invariant") ;
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
ObjectWaiter * Tail ;
for (Tail = _EntryList ; Tail != NULL && Tail->_next != NULL ; Tail =
Tail->_next) ;
if (Tail == NULL) {
_EntryList = w ;
} else {
Tail->_next = w ;
w->_prev = Tail ;
}
}
// qmode =4:cxq队列插入到_EntryList头部
if (QMode == 4 && _cxq != NULL) {
w = _cxq ;
for (;;) {
assert (w != NULL"Invariant") ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL&_cxq, w) ;
if (u == w) break ;
w = u ;
}
assert (w != NULL"invariant") ;
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
if (_EntryList != NULL) {
q->_next = _EntryList ;
_EntryList->_prev = q ;
}
_EntryList = w ;
}
w = _EntryList ;
if (w != NULL) {
assert (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
ExitEpilog (Self, w) ;
return ;
}
w = _cxq ;
if (w == NULL) continue ;
for (;;) {
assert (w != NULL"Invariant") ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL&_cxq,
w) ;
if (u == w) break ;
w = u ;
}
TEVENT (Inflated exit - drain cxq into EntryList) ;
assert (w != NULL"invariant") ;
assert (_EntryList == NULL"invariant") ;
if (QMode == 1) {
// QMode == 1 : drain cxq to EntryList, reversing order
// We also reverse the order of the list.
ObjectWaiter * s = NULL ;
ObjectWaiter * t = w ;
ObjectWaiter * u = NULL ;
while (t != NULL) {
guarantee (t->TState == ObjectWaiter::TS_CXQ, "invariant") ;
t->TState = ObjectWaiter::TS_ENTER ;
u = t->_next ;
t->_prev = u ;
t->_next = s ;
s = t;
t = u ;
}
_EntryList = s ;
assert (s != NULL"invariant") ;
} else {
// QMode == 0 or QMode == 2
_EntryList = w ;
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
}
if (_succ != NULL) continue;
w = _EntryList ;
if (w != NULL) {
guarantee (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
ExitEpilog (Self, w) ;
return ;
}
}
}

  1. 退出同步代码块时会让_recursions 减 1,当 _recursions 的值减为 0 时,说明线程释放了锁。
  2. 根据不同的策略(由 QMode 指定),从 cxq 或 EntryList 中获取头节点,通过 ObjectMonitor::ExitEpilog 方法唤醒该节点封装的线程,唤醒操作最终由 unpark 完成,实现。
    如下。
void ObjectMonitor::ExitEpilog (Thread * Self, ObjectWaiter * Wakee) {
assert (_owner == Self, "invariant") ;
_succ = Knob_SuccEnabled ? Wakee->_thread : NULL ;
ParkEvent * Trigger = Wakee->_event ;
Wakee = NULL ;
// Drop the lock
OrderAccess::release_store_ptr (&_owner, NULL) ;
OrderAccess::fence() ; // ST _owner vs LD in
unpark()
if (SafepointSynchronize::do_call_back()) {
TEVENT (unpark before SAFEPOINT) ;
}
DTRACE_MONITOR_PROBE(contended__exit, this, object(), Self);
Trigger->unpark() ; // 唤醒之前被pack()挂起的线程.
// Maintain stats and report events to JVMTI
if (ObjectMonitor::_sync_Parks != NULL) {
ObjectMonitor::_sync_Parks->inc() ;
}
}

被唤醒的线程,会回到 void ATTR ObjectMonitor::EnterI (TRAPS) 的第 600 行,继续执行 monitor 的竞争。

// park self
if (_Responsible == Self || (SyncFlags & 1)) {
TEVENT (Inflated enter - park TIMED) ;
Self->_ParkEvent->park ((jlong) RecheckInterval) ;
// Increase the RecheckInterval, but clamp the value.
RecheckInterval *= 8 ;
if (RecheckInterval > 1000) RecheckInterval = 1000 ;
} else {
TEVENT (Inflated enter - park UNTIMED) ;
Self->_ParkEvent->park() ;
}
if (TryLock(Self) > 0) break ;


monitor 是重量级锁。

可以看到 ObjectMonitor 的函数调用中会涉及到 Atomic::cmpxchg_ptr,Atomic::inc_ptr 等内核函数,执行同步代码块,没有竞争到锁的对象会 park(); 被挂起,竞争到锁的线程会 unpark(); 唤醒。这个时候就会存在操作系统用户态和内核态的转换,这种切换会消耗大量的系统资源。所以 synchronized 是 Java 语言中是一个重量级(Heavyweight)的操作。

用户态和和内核态是什么东西呢?要想了解用户态和内核态还需要先了解一下 Linux 系统的体系架构。

从上图可以看出,Linux 操作系统的体系架构分为:用户空间(应用程序的活动空间)和内核。

内核:本质上可以理解为一种软件,控制计算机的硬件资源,并提供上层应用程序运行的环境。

用户空间:上层应用程序活动的空间。应用程序的执行必须依托于内核提供的资源,包括CPU资源、存储资源、I/O资源等。

系统调用:为了使上层应用能够访问到这些资源,内核必须为上层应用提供访问的接口:即系统调用。

所有进程初始都运行于用户空间,此时即为用户运行状态(简称:用户态);但是当它调用系统调用执行某些操作时,例如 I/O 调用,此时需要陷入内核中运行,我们就称进程处于内核运行态(或简称为内核态)。 系统调用的过程可以简单理解为

  • 用户态程序将一些数据值放在寄存器中, 或者使用参数创建一个堆栈, 以此表明需要操作系统提供的服务。

  • 用户态程序执行系统调用。

  • CPU 切换到内核态,并跳到位于内存指定位置的指令。

  • 系统调用处理器(system call handler)会读取程序放入内存的数据参数,并执行程序请求的服务。

  • 系统调用完成后,操作系统会重置 CPU 为用户态并返回系统调用的结果。

由此可见用户态切换至内核态需要传递许多变量,同时内核还需要保护好用户态在切换时的一些寄存器值、变量等,以备内核态切换回用户态。这种切换就带来了大量的系统资源消耗,这就是在 synchronized 未优化之前,效率低的原因。



第六章:JDK 6 synchronized 优化。

CAS。
目标。

学习 CAS 的作用。

学习CAS的原理。



CAS 概述和作用。

CAS 的全称是:Compare And Swap(比较相同再交换)。是现代 CPU 广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。

CAS 的作用:CAS 可以将比较和交换转换为原子操作,这个原子操作直接由 CPU 保证。CAS 可以保证共享变量赋值时的原子操作。CAS 操作依赖 3 个值:内存中的值 V,旧的预估值 X,要修改的新值 B,如果旧的预估值 X 等于内存中的值 V,就将新的值 B 保存到内存中。



CAS 和 volatile 实现无锁并发。
package com.geek.synchronize.geek.demo;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * CAS 和 volatile 实现无锁并发。
 *
 * @author geek
 */
public class CasVolatileDemo {

    public static void main(String[] args) {

        AtomicInteger atomicInteger = new AtomicInteger();

        Runnable runnable = () -> {
            for (int i = 0; i < 1000; i++) {
                atomicInteger.incrementAndGet();
            }
        };
        List<Thread> threadList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(runnable);
            thread.start();
            threadList.add(thread);
        }
        for (Thread t : threadList) {
            try {
                t.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("atomicInteger = " + atomicInteger);
        System.out.println("atomicInteger.get() = " + atomicInteger.get());
    }

}

/*
Connected to the target VM, address: '127.0.0.1:62217', transport: 'socket'
atomicInteger = 5000
atomicInteger.get() = 5000
Disconnected from the target VM, address: '127.0.0.1:62217', transport: 'socket'

Process finished with exit code 0
 */



CAS 原理。

通过刚才 AtomicInteger 的源码我们可以看到,Unsafe 类提供了原子操作。



Unsafe 类介绍。

Unsafe 类使 Java 拥有了像 C 语言的指针一样操作内存空间的能力,同时也带来了指针的问题。过度的使用 Unsafe 类会使得出错的几率变大,因此 Java 官方并不建议使用的,官方文档也几乎没有。Unsafe 对象不能直接调用,只能通过反射获得。



Unsafe 实现 CAS。


乐观锁和悲观锁。

悲观锁从悲观的角度出发:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞。因此 synchronized 我们也将其称之为悲观锁。JDK 中的 ReentrantLock 也是一种悲观锁。性能较差!

乐观锁从乐观的角度出发:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,就算改了也没关系,再重试即可。所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去修改这个数据,如何没有人修改则更新,如果有人修改则重试。

CAS这种机制我们也可以将其称之为乐观锁。综合性能较好!

CAS获取共享变量时,为了保证该变量的可见性,需要使用 volatile 修饰。结合 CAS 和 volatile 可以实现无锁并发,适用于竞争不激烈、多核 CPU 的场景下。

  • 因为没有使用 synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一。
  • 但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响。


小结。
  • CAS 的作用。
    Compare And Swap,CAS 可以将比较和交换转换为原子操作,这个原子操作直接由处理器保证。

  • CAS 的原理。
    CAS 需要 3 个值:内存地址 V,旧的预期值 A,要修改的新值 B,如果内存地址 V 和旧的预期值 A 相等就修改内存地址值为 B。

  • synchronized 锁升级过程。
    高效并发是从 JDK 5 到 JDK 6 的一个重要改进,HotSpot 虛拟机开发团队在这个版本上花费了大量的精力去实现各种锁优化技术,包括偏向锁(Biased Locking)、轻量级锁(Lightweight Locking)和如适应性自旋(Adaptive Spinning)、锁消除(Lock Elimination)、锁粗化(Lock Coarsening)等,这些技术都是为了在线程之间更高效地共享数据,以及解决竞争问题,从而提高程序的执行效率。

无锁 --》 偏向锁 --》 轻量级锁 –-》 重量级锁



Java 对象的布局。
目标。

学习 Java 对象的布局。

术语参考: http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html

在 JVM 中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充。如下图所示。



对象头。

当一个线程尝试访问 synchronized 修饰的代码块时,ta 首先要获得锁,那么这个锁到底存在哪里呢?是存在锁对象的对象头中的。

HotSpot 采用 instanceOopDesc 和 arrayOopDesc 来描述对象头,arrayOopDesc 对象用来描述数组类型。instanceOopDesc 的定义的在 Hotspot 源码的 instanceOop.hpp 文件中,另外,arrayOopDesc 的定义对应 arrayOop.hpp。

class instanceOopDesc : public oopDesc {
public:
// aligned header size.
static int header_size() { return sizeof(instanceOopDesc)/HeapWordSize; }
// If compressed, the offset of the fields of the instance may not be aligned.
static int base_offset_in_bytes() {
// offset computation code breaks if UseCompressedClassPointers
// only is true
return (UseCompressedOops && UseCompressedClassPointers) ?
klass_gap_offset_in_bytes() :
sizeof(instanceOopDesc);
}
static bool contains_field_offset(int offset, int nonstatic_field_size) {
int base_in_bytes = base_offset_in_bytes();
return (offset >= base_in_bytes &&
(offset-base_in_bytes) < nonstatic_field_size * heapOopSize);
}
};

从 instanceOopDesc 代码中可以看到 instanceOopDesc 继承自 oopDesc,oopDesc的定义在 Hotspot 源码中的 oop.hpp 文件中。

在普通实例对象中,oopDesc 的定义包含两个成员,分别是 _mark 和 _metadata。

_mark 表示对象标记、属于 markOop 类型,也就是接下来要讲解的 Mark World,ta 记录了对象和锁有关的信息。

_metadata 表示类元信息,类元信息存储的是对象指向它的类元数据(Klass)的首地址,其中 Klass 表示普通指针、 _compressed_klass 表示压缩类指针。

对象头由两部分组成,一部分用于存储自身的运行时数据,称之为 Mark Word,另外一部分是类型指针,及对象指向它的类元数据的指针。



Mark Word。

Mark Word 用于存储对象自身的运行时数据,如哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等,占用内存大小与虚拟机位长一致。Mark Word 对应的类型是 markOop。源码位于 markOop.hpp 中。

// Bit-format of an object header (most significant first, big endian layout
below):
//
// 32 bits:
// --------
// hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal
object)
// JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased
object)
// size:32 ------------------------------------------>| (CMS free
block)
// PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS
promoted object)
//
// 64 bits:
// --------
// unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal
object)
// JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased
object)
// PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS
promoted object)
// size:64 ----------------------------------------------------->| (CMS free
block)
// [JavaThread* | epoch | age | 1 | 01] lock is biased toward given
thread
// [0 | epoch | age | 1 | 01] lock is anonymously biased
//
// - the two lock bits are used to describe three states: locked/unlocked and
monitor.
//
// [ptr | 00] locked ptr points to real header on
stack
// [header | 0 | 01] unlocked regular object header
// [ptr | 10] monitor inflated lock (header is wapped
out)
// [ptr | 11] marked used by markSweep to mark an
object
// not valid at any other time

在 32 位虚拟机下,Mark Word 是 32bit 大小的,其存储结构如下。



klass pointer。

这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM通过这个指针确定对象是哪个类的实例。该指针的位长度为 JVM 的一个字大小,即 32 位的 JVM 为 32 位,64 位的 JVM 为 64 位。如果应用的对象过多,使用 64 位的指针将浪费大量内存,统计而言,64 位的 JVM 将会比 32 位的 JVM 多耗费 50% 的内存。为了节约内存可以使用选项 -XX:+UseCompressedOops 开启指针压缩,其中,oop 即 ordinary object pointer 普通对象指针。开启该选项后,下列指针将压缩至 32 位:

  • 每个 Class 的属性指针(即静态变量)。

  • 每个对象的属性指针(即对象变量)。

  • 普通对象数组的每个元素指针。

当然,也不是所有的指针都会压缩,一些特殊类型的指针 JVM 不会优化,比如指向 PermGen 的 Class 对象指针(JDK8 中指向元空间的 Class 对象指针)、本地变量、堆栈元素、入参、返回值和 NULL 指针等。

对象头 = Mark Word + 类型指针(未开启指针压缩的情况下)

在 32 位系统中,Mark Word = 4 bytes,类型指针 = 4bytes,对象头 = 8 bytes = 64 bits。
在 64 位系统中,Mark Word = 8 bytes,类型指针 = 8bytes,对象头 = 16 bytes = 128bits。



实例数据。

就是类中定义的成员变量。



对齐填充。

对齐填充并不是必然存在的,也没有什么特别的意义,他仅仅起着占位符的作用,由于 HotSpot VM 的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说,就是对象的大小必须是 8 字节的整数倍。而对象头正好是 8 字节的倍数,因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。



查看 Java 对象布局。


小结。

Java 对象由 3 部分组成,对象头,实例数据,对齐数据对象头分成两部分:Mark World + Klass pointer。



偏向锁。


目标。

学习偏向锁的原理和好处。



什么是偏向锁。

偏向锁是 JDK 6 中的重要引进,因为 HotSpot 作者经过研究实践发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低,引进了偏向锁。

偏向锁的“偏”,就是偏心的“偏”、偏袒的“偏”,ta 的意思是这个锁会偏向于第一个获得它的线程,会在对象头存储锁偏向的线程 ID,以后该线程进入和退出同步块时只需要检查是否为偏向锁、锁标志位以及 ThreadID 即可。

不过一旦出现多个线程竞争时必须撤销偏向锁,所以撤销偏向锁消耗的性能必须小于之前节省下来的 CAS 原子操作的性能消耗,不然就得不偿失了。



偏向锁原理。

当线程第一次访问同步块并获取锁时,偏向锁处理流程如下:

  • 虚拟机将会把对象头中的标志位设为“01”,即偏向模式。

  • 同时使用CAS操作把获取到这个锁的线程的 ID 记录在对象的 Mark Word 之中 ,如果 CAS 操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作,偏向锁的效率高。

持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作,偏向锁的效率高。



偏向锁的撤销。
  • 偏向锁的撤销动作必须等待全局安全点。

  • 暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态。

  • 撤销偏向锁,恢复到无锁(标志位为 01)或轻量级锁(标志位为 00)的状态。

偏向锁在 Java 6 之后是默认启用的,但在应用程序启动几秒钟之后才激活,可以使用 -XX:BiasedLockingStartupDelay=0 参数关闭延迟,如果确定应用程序中所有锁通常情况下处于竞争状态,可以通过 XX:-UseBiasedLocking=false 参数关闭偏向锁。



偏向锁好处。

偏向锁是在只有一个线程执行同步块时进一步提高性能,适用于一个线程反复获得同一锁的情况。偏向锁可以提高带有同步但无竞争的程序性能。

ta 同样是一个带有效益权衡性质的优化,也就是说,ta 并不一定总是对程序运行有利,如果程序中大多
数的锁总是被多个不同的线程访问比如线程池,那偏向模式就是多余的。

在 JDK5 中偏向锁默认是关闭的,而到了 JDK6 中偏向锁已经默认开启。但在应用程序启动几秒钟之后才激活,可以使用 -XX:BiasedLockingStartupDelay=0 参数关闭延迟,如果确定应用程序中所有锁通常情况下处于竞争状态,可以通过 XX:-UseBiasedLocking=false 参数关闭偏向锁。



小结。
  • 偏向锁的原理是什么。
    当锁对象第一次被线程获取的时候,虚拟机将会把对象头中的标志位设为“01”,即偏向模式。同时使用 CAS 操作把获取到这个锁的线程的 ID 记录在对象的 Mark Word 之中 ,如果 CAS 操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作,偏向锁的效率高。

  • 偏向锁的好处是什么。
    偏向锁是在只有一个线程执行同步块时进一步提高性能,适用于一个线程反复获得同一锁的情况。偏向锁可以提高带有同步但无竞争的程序性能。



轻量级锁。


目标。

学习轻量级锁的原理和好处。



什么是轻量级锁。

轻量级锁是 JDK 6 之中加入的新型锁机制,ta 名字中的“轻量级”是相对于使用 monitor 的传统锁而言的,因此传统的锁机制就称为“重量级”锁。首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的。

引入轻量级锁的目的:在多线程交替执行同步块的情况下,尽量避免重量级锁引起的性能消耗,但是如果多个线程在同一时刻进入临界区,会导致轻量级锁膨胀升级重量级锁,所以轻量级锁的出现并非是要替代重量级锁。



轻量级锁原理。

当关闭偏向锁功能或者多个线程竞争偏向锁导致偏向锁升级为轻量级锁,则会尝试获取轻量级锁,其步骤如下。

获取锁

  • 判断当前对象是否处于无锁状态(hashcode、0、01),如果是,则 JVM 首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的 Mark Word 的拷贝(官方把这份拷贝加了一个 Displaced 前缀,即 Displaced Mark Word),将对象的 Mark Word 复制到栈帧中的 Lock Record 中,将 Lock Record 中的 owner 指向当前对象。

  • JVM 利用 CAS 操作尝试将对象的 Mark Word 更新为指向 Lock Record 的指针,如果成功表示竞争到锁,则将锁标志位变成 00,执行同步操作。

  • 如果失败则判断当前对象的 Mark Word 是否指向当前线程的栈帧,如果是则表示当前线程已经持有当前对象的锁,则直接执行同步代码块;否则只能说明该锁对象已经被其他线程抢占了,这时轻量级锁需要膨胀为重量级锁,锁标志位变成 10,后面等待的线程将会进入阻塞状态。



轻量级锁的释放。

轻量级锁的释放也是通过 CAS 操作来进行的,主要步骤如下。

  • 取出在获取轻量级锁保存在 Displaced Mark Word 中的数据。

  • 用 CAS 操作将取出的数据替换当前对象的 Mark Word 中,如果成功,则说明释放锁成功。

  • 如果 CAS 操作替换失败,说明有其他线程尝试获取该锁,则需要将轻量级锁需要膨胀升级为重量级锁。

对于轻量级锁,其性能提升的依据是“对于绝大部分的锁,在整个生命周期内都是不会存在竞争的”,如果打破这个依据则除了互斥的开销外,还有额外的 CAS 操作,因此在有多线程竞争的情况下,轻量级锁比重量级锁更慢。



轻量级锁好处。

在多线程交替执行同步块的情况下,可以避免重量级锁引起的性能消耗。



小结。
  • 轻量级锁的原理是什么。
    将对象的 Mark Word 复制到栈帧中的 Lock Record 中。Mark Word 更新为指向 Lock Record 的指针。

  • 轻量级锁好处是什么?
    在多线程交替执行同步块的情况下,可以避免重量级锁引起的性能消耗。



自旋锁。
目标。

学习自旋锁原理。



自旋锁原理。

前面我们讨论 monitor 实现锁的时候,知道 monitor 会阻塞和唤醒线程,线程的阻塞和唤醒需要 CPU 从用户态转为核心态,频繁的阻塞和唤醒对 CPU 来说是一件负担很重的工作,这些操作给系统的并发性能带来了很大的压力。同时,虚拟机的开发团队也注意到在许多应用上,共享数据的锁定状态只会持续很短的一段时间,为了这段时间阻塞和唤醒线程并不值得。如果物理机器有一个以上的处理器,能让两个或以上的线程同时并行执行,我们就可以让后面请求锁的那个线程“稍等一下”,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只需让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。

自旋锁在 JDK 1.4.2 中就已经引入,只不过默认是关闭的,可以使用 -XX:+UseSpinning 参数来开启,在 JDK 6中 就已经改为默认开启了。自旋等待不能代替阻塞,且先不说对处理器数量的要求,自旋等待本身虽然避免了线程切换的开销,但它是要占用处理器时间的,因此,如果锁被占用的时间很短,自旋等待的效果就会非常好,反之,如果锁被占用的时间很长。那么自旋的线程只会白白消耗处理器资源,而不会做任何有用的工作,反而会带来性 能上的浪费。因此,自旋等待的时间必须要有一定的限度,如果在多线程交替执行同步块的情况下,可以避免重量级锁引起的性能消耗。

synchronized (Demo01.class) {
    System.out.println("aaa");
}

自旋超过了限定的次数仍然没有成功获得锁,就应当使用传统的方式去挂起线程了。自旋次数的默认值是 10 次,用户可以使用参数 -XX:PreBlockSpin 来更改。



适应性自旋锁。

在 JDK 6 中引入了自适应的自旋锁。自适应意味着自旋的时间不再固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进而它将允许自旋等待持续相对更长的时间,比如 100 次循环。另外,如果对于某个锁,自旋很少成功获得过,那在以后要获取这个锁时将可能省略掉自旋过程,以避免浪费处理器资源。有了自适应自旋,随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的状况预测就会越来越准确,虛拟机就会变得越来越“聪明”了。



锁消除。
目标。

学习如何进行锁消除。

锁消除是指虚拟机即时编译器(JIT)在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。锁消除的主要判定依据来源于逃逸分析的数据支持,如果判断在一段代码中,堆上的所有数据都不会逃逸出去从而被其他线程访问到,那就可以把它们当做栈上数据对待,认为它们是线程私有的,同步加锁自然就无须进行。变量是否逃逸,对于虚拟机来说需要使用数据流分析来确定,但是程序员自己应该是很清楚的,怎么会在明知道不存在数据争用的情况下要求同步呢?实际上有许多同步措施并不是程序员自己加入的,同步的代码在 Java 程序中的普遍程度也许超过了大部分读者的想象。下面这段非常简单的代码仅仅是输出 3 个字符串相加的结果,无论是源码字面上还是程序语义上都没有同步。

StringBuffer 的 append(); 是一个同步方法,锁就是 this 也就是new StringBuilder()。虚拟机发现 ta 的动态作用域被限制在 concatString(); 方法内部。也就是说,new StringBuilder(); 对象的引用永远不会“逃逸”到 concatString(); 方法之外,其他线程无法访问到 ta,因此,虽然这里有锁,但是可以被安全地消除掉,在即时编译之后,这段代码就会忽略掉所有的同步而直接执行了。



锁粗化。
目标。

学习锁粗化的原理。

public class Demo01 {
    public static void main(String[] args) {
    contactString("aa", "bb", "cc");
    }
    public static String contactString(String s1, String s2, String s3) {
    return new StringBuffer().append(s1).append(s2).append(s3).toString();
    }
}

原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小,只在共享数据的实际作用域中才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待锁的线程也能尽快拿到锁。大部分情况下,上面的原则都是正确的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体中的,那即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。



小结。
  • 什么是锁粗化。
    JVM 会探测到一连串细小的操作都使用同一个对象加锁,将同步代码块的范围放大,放到这串操作的外面,这样只需要加一次锁即可。

  • 平时写代码如何对 synchronized 优化。
    减少 synchronized 的范围。
    同步代码块中尽量短,减少同步代码块中代码的执行时间,减少锁的竞争。
    降低 synchronized 锁的粒度。
    将一个锁拆分为多个锁提高并发度。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

lyfGeek

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

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

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

打赏作者

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

抵扣说明:

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

余额充值