【并发编程】吃透Synchronized

是什么

Synchronized是同步关键字,是一种重量级锁,Synchronized底层是由原语实现

的,保证了原子性,具体使用的是monitorenter 和 monitorexit 指令,其中

monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块

的结束位置。 当执行 monitorenter 指令时,线程试图获取锁,也就是获取 monitor (

monitor 对象存在于每个 Java 对象的对象头中,synchronized 锁便是通过这种方式

获取锁的,这也是为什么 Java 中任意对象都可以作为锁的原因) 的持有权。当计数

器为0,则可以成功获取,获取后将锁计数器设为1,也就是加1;相应的,在执行

monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那

当前线程就要阻塞等待,直到锁被另外一个线程释放为止

场景

修饰代码块

public class Synchronized {
    public void husband(){
        synchronized(new test()){

        }
    }
}

修饰方法,是对象锁,锁的是这个对象

public class Synchronized {
    public synchronized void husband(){

    }
}

修饰类,是类锁,锁的是Class

public class Synchronized {
    public void husband(){
        synchronized(Synchronized.class){

        }
    }
}

底层实现

javap -c xxx.class 命令查看反编译的文件

MacBook-Pro-3:juc aobing$ javap -p -v -c Synchronized.class
Classfile /Users/aobing/IdeaProjects/Thanos/laogong/target/classes/juc/Synchronized.class
  Last modified 2020-5-17; size 375 bytes
  MD5 checksum 4f5451a229e80c0a6045b29987383d1a
  Compiled from "Synchronized.java"
public class juc.Synchronized
  minor version: 0
  major version: 49
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#14         // java/lang/Object."<init>":()V
   #2 = Class              #15            // juc/Synchronized
   #3 = Class              #16            // java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               LocalVariableTable
   #9 = Utf8               this
  #10 = Utf8               Ljuc/Synchronized;
  #11 = Utf8               husband
  #12 = Utf8               SourceFile
  #13 = Utf8               Synchronized.java
  #14 = NameAndType        #4:#5          // "<init>":()V
  #15 = Utf8               juc/Synchronized
  #16 = Utf8               java/lang/Object
{
  public juc.Synchronized();
    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 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ljuc/Synchronized;

  public synchronized void husband();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED  // 这里
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #2                  // class juc/Synchronized
         2: dup
         3: astore_1
         4: monitorenter   // 这里
         5: aload_1
         6: monitorexit    // 这里
         7: goto          15
        10: astore_2
        11: aload_1
        12: monitorexit    // 这里
        13: aload_2
        14: athrow
        15: return
      Exception table:
         from    to  target type
             5     7    10   any
            10    13    10   any
      LineNumberTable:
        line 10: 0
        line 12: 5
        line 13: 15
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      16     0  this   Ljuc/Synchronized;
}
SourceFile: "Synchronized.java"

当我们进入一个人方法的时候,执行monitorenter,就会获取当前对象的一个所有权,这个时候monitor进入数为1,当前的这个线程就是这个monitor的owner。

如果你已经是这个monitor的owner了,你再次进入,就会把进入数+1.

同理,当他执行完monitorexit,对应的进入数就-1,直到为0,才可以被其他线程持有。

特性

Java内存模型是对共享数据的可见性、有序性、和原子性的规则和保障

有序性

CPU会为了优化我们的代码,会对我们程序进行重排序,Synchronized会使用内存屏障

java虚拟机会在 MonitorEnter( 它包含了读操作 ) 对应的机器码指令之后临界区开始之前的地方插入一个获取屏障,并在临界区结束之后 MonitorExit ( 它包含了写操作 ) 对应的机器码指令之前的地方插入一个释放屏障

内存屏障的作用是禁止该读操作与其后的任何读写操作之间进行重排序

原子性

Synchronized确保同一时间只有一个线程能拿到锁,能够进入代码块,使用的是monitorenter 和 monitorexit 指令实现的

可见性

线程解锁前,必须把共享变量的最新值刷新到主内存中

线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新获取最新的值

注意:加锁与解锁需要是同一把锁

可重入性

synchronized锁对象的时候有个计数器,他会记录下线程获取锁的次数,在执行完对应的代码块之后,计数器就会-1,直到计数器清零,就释放锁了。

不可中断性

不可中断就是指,一个线程获取锁之后,另外一个线程处于阻塞或者等待状态,前一个不释放,后一个也一直会阻塞或者等待,不可以被中断。

Synchronized有一个特点就是锁的释放不是由程序控制的,这也就造成了线程不释放锁,会一直阻塞的问题

synchronized和Lock的区别

synchronized是关键字,是JVM层面的底层啥都帮我们做了,而Lock是一个接口,是JDK层面的有丰富的API。

synchronized会自动释放锁,而Lock必须手动释放锁。

synchronized是不可中断的,Lock可以中断也可以不中断。

通过Lock可以知道线程有没有拿到锁,而synchronized不能。

synchronized能锁住方法和代码块,而Lock只能锁住代码块。

Lock可以使用读锁提高多线程读效率。

synchronized是非公平锁,ReentrantLock可以控制是否是公平锁。

猜你喜欢

转载自blog.csdn.net/yujing1314/article/details/107469204