持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情
synchronized的作用
加锁,一旦说某个线程加了一把锁之后,就会保证,其他的线程没法去读取和修改这个变量的值了,同一时间,只有一个线程可以读这个数据以及修改这个数据,别的线程都会卡在尝试获取锁那儿。
synchronized作用域
修饰代码块:大括号括起来的代码,作用于调用的对象。 修饰方法:整个方法,作用于调用的对象。 修饰静态方法:整个静态方法,作用于所有对象。 修饰类:括号括起来的部分,作用于所有对象。
synchronized原理
加锁和释放锁的原理
package com.example.demo.thread;
public class SynchronizeDemo {
Object object = new Object();
public void method1() {
synchronized (object) {
}
method2();
}
private static void method2() {
}
}
通过javac编译为class命令 ,在通过javap反编译查看字节码文件
Classfile /G:/SourceCode/java/demo/demo/src/main/java/com/example/demo/thread/SynchronizeDemo.class
Last modified 2022-10-5; size 521 bytes
MD5 checksum 028a2ff51e38ca40ca79392f9f7e4960
Compiled from "SynchronizeDemo.java"
public class com.example.demo.thread.SynchronizeDemo
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #2.#20 // java/lang/Object."<init>":()V
#2 = Class #21 // java/lang/Object
#3 = Fieldref #5.#22 // com/example/demo/thread/SynchronizeDemo.object:Ljava/lang/Object;
#4 = Methodref #5.#23 // com/example/demo/thread/SynchronizeDemo.method2:()V
#5 = Class #24 // com/example/demo/thread/SynchronizeDemo
#6 = Utf8 object
#7 = Utf8 Ljava/lang/Object;
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 method1
#13 = Utf8 StackMapTable
#14 = Class #24 // com/example/demo/thread/SynchronizeDemo
#15 = Class #21 // java/lang/Object
#16 = Class #25 // java/lang/Throwable
#17 = Utf8 method2
#18 = Utf8 SourceFile
#19 = Utf8 SynchronizeDemo.java
#20 = NameAndType #8:#9 // "<init>":()V
#21 = Utf8 java/lang/Object
#22 = NameAndType #6:#7 // object:Ljava/lang/Object;
#23 = NameAndType #17:#9 // method2:()V
#24 = Utf8 com/example/demo/thread/SynchronizeDemo
#25 = Utf8 java/lang/Throwable
{
java.lang.Object object;
descriptor: Ljava/lang/Object;
flags:
public com.example.demo.thread.SynchronizeDemo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: new #2 // class java/lang/Object
8: dup
9: invokespecial #1 // Method java/lang/Object."<init>":()V
12: putfield #3 // Field object:Ljava/lang/Object;
15: return
LineNumberTable:
line 3: 0
line 5: 4
public void method1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: getfield #3 // Field object:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: aload_1
8: monitorexit
9: goto 17
12: astore_2
13: aload_1
14: monitorexit
15: aload_2
16: athrow
17: invokestatic #4 // Method method2:()V
20: return
Exception table:
from to target type
7 9 12 any
12 15 12 any
LineNumberTable:
line 7: 0
line 9: 7
line 10: 17
line 11: 20
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 12
locals = [ class com/example/demo/thread/SynchronizeDemo, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
}
SourceFile: "SynchronizeDemo.java"
字节码文件中可以看到两个值 分别为monitorenter和monitorexit
每个对象都有一个关联的monitor,比如一个对象实例就有一个monitor,一个类的Class对象也有一个monitor,如果要对这个对象加锁,那么必须获取这个对象关联的monitor的lock锁
monitor里面有一个计数器,从0开始的。如果一个线程要获取monitor的锁,就看看他的计数器是不是0,如果是0的话,那么说明没人获取锁,他就可以获取锁了,然后对计数器加1
这个monitor的锁是支持重入加锁
保证可见性原理
Synchronized的happens-before规则,即监视器锁规则:对同一个监视器的解锁,happens-before于对该监视器的加锁
java中锁的优化
jdk1.6中对锁的实现引入了大量的优化,如锁粗化、锁消除、轻量锁、偏向锁、自旋锁等
- 锁粗化:减少不必要的紧连在一起的unlock,lock操作,将多个连续的锁扩展成一个范围更大的锁。
- 锁消除:通过运行时JIT编译器的逃逸分析来消除一些没有在当前同步块以外被其他线程共享的数据的锁保护,通过逃逸分析也可以在线程本的Stack上进行对象空间的分配(同时还可以减少Heap上的垃圾收集开销)。
- 轻量锁:这种锁实现的背后基于这样一种假设,即在真实的情况下我们程序中的大部分同步代码一般都处于无锁竞争状态(即单线程执行环境),在无锁竞争的情况下完全可以避免调用操作系统层面的重量级互斥锁,取而代之的是在monitorenter和monitorexit中只需要依靠一条CAS原子指令就可以完成锁的获取及释放。当存在锁竞争的情况下,执行CAS指令失败的线程将调用操作系统互斥锁进入到阻塞状态,当锁被释放的时候被唤醒。
- 偏向锁:为了在无锁竞争的情况下避免在锁获取过程中执行不必要的CAS原子指令,因为CAS原子指令虽然相对于重量级锁来说开销比较小但还是存在非常可观的本地延迟。
- 自旋锁:当线程在获取轻量级锁的过程中执行CAS操作失败时,在进入与monitor相关联的操作系统重量级锁(mutex semaphore)前会进入忙等待(Spinning)然后再次尝试,当尝试一定的次数后如果仍然没有成功则调用与该monitor关联的semaphore(即互斥锁)进入到阻塞状态。
Synchronied锁的状态
无锁、偏向锁、轻量级锁、重量级锁
锁膨胀方向:
无锁->偏向锁->轻量级锁->重量级锁
锁升级过程
1、偏向锁:在锁对象的对象头中记录一下当前获取到该锁的线程id,该线程下次如果又来获取该锁就可以直接获取到了
2、轻量级锁:由偏向锁升级而来,当一个线程获取到锁后,此时这把锁是偏向锁,此时如果又第二个线程来竞争,偏向锁会升级为轻量级锁,之所以叫轻量级锁,是为了和重量级锁区分开来,轻量级锁底层通过自旋来实现的,并不会阻塞线程
3、如果自旋次数过多仍然没有获取到锁,则会升级为重量级锁,重量级锁会导致线程阻塞
4、自旋锁:自旋锁就是线程获取锁的过程中,不回去阻塞线程,也就无所谓唤醒线程,阻塞和唤醒这两个步骤都是需要操作系统去进行的,比较小号时间,自旋锁是线程通过cas获取预期的一个标记,如果没有获取到,则继续循环获取,如果获取到了则表示获取了锁,这个过程线程一直在运行中,相对而言没有使用太多的操作系统资源,比较轻量
Synchronize和ReentrantLock的区别
1、synchronize是一个关键字,ReentrantLock是一个类
2、synchronize会自动的加锁和释放锁,ReentrantLock需要手动加锁和释放锁
3、synchronize的底层是jvm层面的锁,ReentrantLock是api层面的锁
4、synchronize是非公平锁,ReentrantLock可以选择公平锁或非公平锁
5、synchronize锁的是对象,锁信息保存在对象头中,ReentrantLock是通过代码中int类型的state标识锁的状态
6、synchronize底层有一个锁升级的过程