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."":()V
#2 = Class #15 // juc/Synchronized
#3 = Class #16 // java/lang/Object
#4 = Utf8
#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 // "":()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."":()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是关键字,是JVM层面的底层啥都帮我们做了,而Lock是一个接口,是JDK层面的有丰富的API。
synchronized会自动释放锁,而Lock必须手动释放锁。
synchronized是不可中断的,Lock可以中断也可以不中断。
通过Lock可以知道线程有没有拿到锁,而synchronized不能。
synchronized能锁住方法和代码块,而Lock只能锁住代码块。
Lock可以使用读锁提高多线程读效率。
synchronized是非公平锁,ReentrantLock可以控制是否是公平锁。