Java锁
Java锁
悲观锁
乐观锁
自旋锁
可重入锁(递归锁)
写锁(独占锁)/读锁(共享锁)
公平锁/非公平锁
死锁
偏向锁
轻量锁
重量锁
邮戳(票据)锁
1、乐观锁和悲观锁
1.1 悲观锁
悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。
悲观锁的实现方式
synchronized关键字
Lock的实现类都是悲观锁
适合写操作多的场景,先加锁可以保证写操作时数据正确。显示的锁定之后再操作同步资源。
//=============悲观锁的调用方式
public synchronized void m1()
{
//加锁后的业务逻辑......
}
// 保证多个线程使用的是同一个lock对象的前提下
ReentrantLock lock = new ReentrantLock();
public void m2() {
lock.lock();
try {
// 操作同步资源
}finally {
lock.unlock();
}
}
1.2 乐观锁
乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作
乐观锁的实现方式
版本号机制Version。(只要有人提交了就会修改版本号,可以解决ABA问题)
ABA
问题:再CAS中想读取一个值A,想把值A变为C,不能保证读取时的A就是赋值时的A,中间可能有个线程将A变为B再变为A。- 解决方法:Juc包提供了一个
AtomicStampedReference
,原子更新带有版本号的引用类型,通过控制版本值的变化来解决ABA问题。
- 解决方法:Juc包提供了一个
最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。
适合读操作多的场景,不加锁的性能特点能够使其操作的性能大幅提升。
//=============乐观锁的调用方式
// 保证多个线程使用的是同一个AtomicInteger
private AtomicInteger atomicInteger = new AtomicInteger();
atomicInteger.incrementAndGet();
2、锁相关的8钟案例
阿里巴巴代码规范
【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
说明:尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用 RPC 方法。
2.1 案例一
标准访问有ab两个线程,请问先打印邮件还是短信? 邮件
class Phone1 { //资源类
public synchronized void sendEmail() {
System.out.println("-----发送邮件");
}
public synchronized void sendSMS() {
System.out.println("-----发送短信");
}
}
public class Demo1 {
public static void main(String[] args) {
Phone1 phone = new Phone1();
new Thread(phone::sendEmail, "a").start();
//暂停毫秒,保证a线程先启动
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(phone::sendSMS, "b").start();
}
}

2.2 案例二
sendEmail方法中加入暂停3秒钟,请问先打印邮件还是短信 ?邮件
class Phone2 { //资源类
public synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-----发送邮件");
}
public synchronized void sendSMS() {
System.out.println("-----发送短信");
}
}
public class Demo2 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(phone::sendEmail, "a").start();
//暂停毫秒,保证a线程先启动
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(phone::sendSMS, "b").start();
}
}
打印结果:

2.3 案例三
添加一个普通的hello方法,请问先打印邮件还是hello? hello
class Phone3 { //资源类
public synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-----发送邮件");
}
public synchronized void sendSMS() {
System.out.println("-----发送短信");
}
public void hello() {
System.out.println("-------hello");
}
}
public class Demo3 {
public static void main(String[] args) {
Phone3 phone = new Phone3();
new Thread(phone::sendEmail, "a").start();
//暂停毫秒,保证a线程先启动
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(phone::hello, "b").start();
}
}
打印结果:

2.4 案例四
有两部手机,请问先打印邮件还是短信?短信
class Phone4 { //资源类
public synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-----发送邮件");
}
public synchronized void sendSMS() {
System.out.println("-----发送短信");
}
public void hello() {
System.out.println("-------hello");
}
}
public class Demo4 {
public static void main(String[] args) {
Phone4 phone = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(phone::sendEmail, "a").start();
//暂停毫秒,保证a线程先启动
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(phone2::sendSMS, "b").start();
}
}
打印结果:

2.5 案例五
有两个静态同步方法,有1部手机,请问先打印邮件还是短信? 邮件
class Phone5 { //资源类
public static synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-----发送邮件");
}
public static synchronized void sendSMS() {
System.out.println("-----发送短信");
}
}
public class Demo5 {
public static void main(String[] args) {
Phone5 phone = new Phone5();
new Thread(() -> phone.sendEmail(), "a").start();
//暂停毫秒,保证a线程先启动
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> phone.sendSMS(), "b").start();
}
}
打印结果:

2.6 案例六
有两个静态同步方法,有2部手机,请问先打印邮件还是短信? 邮件
class Phone6 { //资源类
public static synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-----发送邮件");
}
public static synchronized void sendSMS() {
System.out.println("-----发送短信");
}
}
public class Demo6 {
public static void main(String[] args) {
Phone6 phone = new Phone6();
Phone6 phone2 = new Phone6();
new Thread(() -> phone.sendEmail(), "a").start();
//暂停毫秒,保证a线程先启动
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> phone2.sendSMS(), "b").start();
}
}
打印结果:

2.7 案例七
有1个静态同步方法,有1个普通同步方法,有1部手机,请问先打印邮件还是短信? 短信
class Phone7 { //资源类
public static synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-----发送邮件");
}
public synchronized void sendSMS() {
System.out.println("-----发送短信");
}
}
public class Demo7 {
public static void main(String[] args) {
Phone7 phone = new Phone7();
new Thread(() -> phone.sendEmail(), "a").start();
//暂停毫秒,保证a线程先启动
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> phone.sendSMS(), "b").start();
}
}
打印结果:

2.8 案例八
有1个静态同步方法,有1个普通同步方法,有2部手机,请问先打印邮件还是短信? 短信
class Phone8 { //资源类
public static synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-----发送邮件");
}
public synchronized void sendSMS() {
System.out.println("-----发送短信");
}
}
public class Demo8 {
public static void main(String[] args) {
Phone8 phone = new Phone8();
Phone8 phone2 = new Phone8();
new Thread(() -> phone.sendEmail(), "a").start();
//暂停毫秒,保证a线程先启动
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> phone2.sendSMS(), "b").start();
}
}
打印结果:

2.9 原理
1.2中
- 一个对象里面如果有多个synchronized方法,某一时刻内,只要一个线程去调用其中的一个synchronized方法了,其他的线程都只能是等待,换句话说,某一个时刻内,只能有唯一的一个线程去访问这些synchronized方法,锁的是当前对象this,被锁定后,其它的线程都不能 进入到当前对象的其他synchronized方法
3中
- hello并未和其他synchronized修饰的方法产生争抢
4 中
- 锁在两个不同的对象/两个不同的资源上,不产生竞争条件
5.6中static+synchronized - 类锁 phone = new Phone();中 加到了左边的Phone上
对于普通同步方法,锁的是当前实例对象,通常指this,具体的一部部手机,所有的普通同步方法用的都是同一把锁→实例对象本身。
对于静态同步方法,锁的是当前类的Class对象,如Phone,class唯一的一个模板。
对于同步方法块,锁的是synchronized括号内的对象。synchronized(o)
7.8中一个加了对象锁,一个加了类锁,不产生竞争条件
2.10 3个体现
- 8种锁的案例实际体现在3个地方-相当于总结
- 作用域实例方法,当前实例加锁,进入同步代码块前要获得当前实例的锁。
- 作用于代码块,对括号里配置的对象加锁。
- 作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁
2.11 字节码角度分析synchronized实现
2.11.1 文件反编译技巧
- 文件反编译
javap -c ***.class
文件反编译,-c表示对代码进行反汇编 - 假如需要更多信息
javap -v ***.class
,-v即-verbose输出附加信息(包括行号、本地变量表、反汇编等详细信息)
2.11.2 synchronized同步代码块
public class SynchronizedClassDemo {
Object object = new Object();
public void m1() {
synchronized (object) {
System.out.println("----hello synchronized code block");
throw new RuntimeException("-----exp");
}
}
public synchronized void m2() {
System.out.println("----hello synchronized m2");
}
public static synchronized void m3() {
System.out.println("----hello static synchronized m3");
}
public static void main(String[] args) {
}
}
- 从target中找到
SynchronizedClassDemo.class
文件,右键,open in terminal,然后javap -c SynchronizedClassDemo.class
PS D:\Projects\Idea_Projects\Jin\java\target\classes\com\jin\juc\locks\analyse> javap -c .\SynchronizedClassDemo.class
Compiled from "SynchronizedClassDemo.java"score6
public class com.jin.juc.locks.analyse.SynchronizedClassDemo {
java.lang.Object object;
public com.jin.juc.locks.analyse.SynchronizedClassDemo();
Code:
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
public void m1();
Code:
0: aload_0
1: getfield #3 // Field object:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
10: ldc #5 // String ----hello synchronized code block
12: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
15: new #7 // class java/lang/RuntimeException
18: dup
19: ldc #8 // String -----exp
21: invokespecial #9 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
24: athrow
25: astore_2
26: aload_1
27: monitorexit
28: aload_2
29: athrow
Exception table:
from to target type
7 28 25 any
public synchronized void m2();
Code:
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #10 // String ----hello synchronized m2
5: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
public static synchronized void m3();
Code:
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #11 // String ----hello static synchronized m3
5: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
public static void main(java.lang.String[]);
Code:
0: return
}
PS D:\Projects\Idea_Projects\Jin\java\target\classes\com\jin\juc\locks\analyse>
- 总结
synchronized
同步代码块,实现使用的是moniterenter
和moniterexit
指令(moniterexit
可能有两个)
2.11.3 synchronized普通同步方法
public class ObjectSyncDemo {
public synchronized void m2() {
System.out.println("------hello synchronized m2");
}
public static void main(String[] args) {
}
}
- 类似于上述操作,最后调用
javap -v .\ObjectSyncDemo.class
PS D:\Projects\Idea_Projects\Jin\java\target\classes\com\jin\juc\locks\analyse> javap -v .\ObjectSyncDemo.class
Classfile /D:/Projects/Idea_Projects/Jin/java/target/classes/com/jin/juc/locks/analyse/ObjectSyncDemo.class
Last modified 2022-7-8; size 705 bytes
MD5 checksum bf5b93c8122ac3423dde6a22f3b2034f
Compiled from "ObjectSyncDemo.java"
public class com.jin.juc.locks.analyse.ObjectSyncDemo
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#22 // java/lang/Object."<init>":()V
#2 = Fieldref #23.#24 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #25 // ------hello synchronized m2
#4 = Methodref #26.#27 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #28 // com/jin/juc/locks/analyse/ObjectSyncDemo
#6 = Class #29 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/jin/juc/locks/analyse/ObjectSyncDemo;
#14 = Utf8 m2
#15 = Utf8 main
#16 = Utf8 ([Ljava/lang/String;)V
#17 = Utf8 args
#18 = Utf8 [Ljava/lang/String;
#19 = Utf8 MethodParameters
#20 = Utf8 SourceFile
#21 = Utf8 ObjectSyncDemo.java
#22 = NameAndType #7:#8 // "<init>":()V
#23 = Class #30 // java/lang/System
#24 = NameAndType #31:#32 // out:Ljava/io/PrintStream;
#25 = Utf8 ------hello synchronized m2
#26 = Class #33 // java/io/PrintStream
#27 = NameAndType #34:#35 // println:(Ljava/lang/String;)V
#28 = Utf8 com/jin/juc/locks/analyse/ObjectSyncDemo
#29 = Utf8 java/lang/Object
#30 = Utf8 java/lang/System
#31 = Utf8 out
#32 = Utf8 Ljava/io/PrintStream;
#33 = Utf8 java/io/PrintStream
#34 = Utf8 println
#35 = Utf8 (Ljava/lang/String;)V
{
public com.jin.juc.locks.analyse.ObjectSyncDemo();
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 9: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/jin/juc/locks/analyse/ObjectSyncDemo;
public synchronized void m2();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String ------hello synchronized m2
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 11: 0
line 12: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/jin/juc/locks/analyse/ObjectSyncDemo;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 16: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 args [Ljava/lang/String;
MethodParameters:
Name Flags
args
}
SourceFile: "ObjectSyncDemo.java"
PS D:\Projects\Idea_Projects\Jin\java\target\classes\com\jin\juc\locks\analyse>
- 总结
调用指令将会检查方法的访问标志是否被设置。如果设置了,执行线程会将先持有
monitore
然后再执行方法,最后在方法完成(无论是正常完成还是非正常完成)时释放monitor
2.11.4 synchronized静态同步方法
public class StaticSyncDemo {
public synchronized void m2() {
System.out.println("------hello synchronized m2");
}
public static synchronized void m3() {
System.out.println("------hello synchronized m3---static");
}
public static void main(String[] args) {
}
}
- 和上面一样的操作
PS D:\Projects\Idea_Projects\Jin\java\target\classes\com\jin\juc\locks\analyse> javap -v .\StaticSyncDemo.class
Classfile /D:/Projects/Idea_Projects/Jin/java/target/classes/com/jin/juc/locks/analyse/StaticSyncDemo.class
Last modified 2022-7-8; size 803 bytes
MD5 checksum e604c22936a7cd5db424ed52eb36c1ea
Compiled from "StaticSyncDemo.java"
public class com.jin.juc.locks.analyse.StaticSyncDemo
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #7.#24 // java/lang/Object."<init>":()V
#2 = Fieldref #25.#26 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #27 // ------hello synchronized m2
#4 = Methodref #28.#29 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = String #30 // ------hello synchronized m3---static
#6 = Class #31 // com/jin/juc/locks/analyse/StaticSyncDemo
#7 = Class #32 // java/lang/Object
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 LocalVariableTable
#13 = Utf8 this
#14 = Utf8 Lcom/jin/juc/locks/analyse/StaticSyncDemo;
#15 = Utf8 m2
#16 = Utf8 m3
#17 = Utf8 main
#18 = Utf8 ([Ljava/lang/String;)V
#19 = Utf8 args
#20 = Utf8 [Ljava/lang/String;
#21 = Utf8 MethodParameters
#22 = Utf8 SourceFile
#23 = Utf8 StaticSyncDemo.java
#24 = NameAndType #8:#9 // "<init>":()V
#25 = Class #33 // java/lang/System
#26 = NameAndType #34:#35 // out:Ljava/io/PrintStream;
#27 = Utf8 ------hello synchronized m2
#28 = Class #36 // java/io/PrintStream
#29 = NameAndType #37:#38 // println:(Ljava/lang/String;)V
#30 = Utf8 ------hello synchronized m3---static
#31 = Utf8 com/jin/juc/locks/analyse/StaticSyncDemo
#32 = Utf8 java/lang/Object
#33 = Utf8 java/lang/System
#34 = Utf8 out
#35 = Utf8 Ljava/io/PrintStream;
#36 = Utf8 java/io/PrintStream
#37 = Utf8 println
#38 = Utf8 (Ljava/lang/String;)V
{
public com.jin.juc.locks.analyse.StaticSyncDemo();
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 9: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/jin/juc/locks/analyse/StaticSyncDemo;
public synchronized void m2();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String ------hello synchronized m2
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 11: 0
line 12: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/jin/juc/locks/analyse/StaticSyncDemo;
public static synchronized void m3();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=0, args_size=0
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #5 // String ------hello synchronized m3---static
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 15: 0
line 16: 8
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 21: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 args [Ljava/lang/String;
MethodParameters:
Name Flags
args
}
SourceFile: "StaticSyncDemo.java"
PS D:\Projects\Idea_Projects\Jin\java\target\classes\com\jin\juc\locks\analyse>
- 总结
- 访问标志区分该方法是否是静态同步方法。
- 注意: m3()方法底层:flags:
ACC_PUBLIC
,ACC_STATIC
,ACC_SYNCHRONIZED
2.11.5 反编译synchronized锁的是什么
概念-管程
- 管程概念
- 管程:Monitor(监视器),也就是我们平时说的锁。监视器锁
- 信号量及其操作原语“封装”在一个对象内部)管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。 管程提供了一种机制,管程可以看做一个软件模块,它是将共享的变量和对于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制。
- 执行线程就要求先成功持有管程,然后才能执行方法,最后当方法完成(无论是正常完成还是非正常完成)时释放管理。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获取到同一个管程。
大厂:为什么任何一个对象都可以成为一个锁?
- 溯源
- Java
Object
类是所有类的父类,也就是说 Java 的所有类都继承了 Object,子类可以使用 Object 的所有方法。 ObjectMonitor.java
→ObjectMonitor.cpp
→objectMonitor.hpp
- Java
ObjectMonitor.cpp
中引入了头文件(include)objectMonitor.hpp
140行
ObjectMonitor() {
_header = NULL;
_count = 0; //用来记录该线程获取锁的次数
_waiters = 0,
_recursions = 0;//锁的重入次数
_object = NULL;
_owner = NULL; //------最重要的----指向持有ObjectMonitor对象的线程,记录哪个线程持有了我
_WaitSet = NULL; //存放处于wait状态的线程队列
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ;//存放处于等待锁block状态的线程队列
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
- 追溯底层可以发现每个对象天生都带着一个对象监视器。
2.11.6 熟悉锁升级
- synchronized必须作用于某个对象中,所以Java在对象的头文件存储了锁的相关信息。锁升级功能主要依赖于 MarkWord 中的锁标志位和释放偏向锁标志位
3、公平锁和非公平锁
3.1、ReentrantLock抢票案例
class Ticket //资源类,模拟3个售票员卖完50张票
{
private int number = 50;
Object lockObject = new Object();
// ReentrantLock lock = new ReentrantLock();//默认非公平锁
ReentrantLock lock = new ReentrantLock(true);//公平锁
public void sale() {
/* synchronized (lockObject) {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出第:\t" + (number--) + "\t 还剩下:" + number);
}
}*/
lock.lock();
try {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出第:\t" + (number--) + "\t 还剩下:" + number);
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}
public class SaleTicketDemo {
public static void main(String[] args)//一切程序的入口
{
Ticket ticket = new Ticket();
new Thread(() -> {
for (int i = 0; i < 55; i++) ticket.sale();
}, "a").start();
new Thread(() -> {
for (int i = 0; i < 55; i++) ticket.sale();
}, "b").start();
new Thread(() -> {
for (int i = 0; i < 55; i++) ticket.sale();
}, "c").start();
}
}
3.1、非公平锁
默认是非公平锁
非公平锁可以插队,买卖票不均匀。
是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级翻转或饥饿的状态(某个线程一直得不到锁)
3.2、公平锁
ReentrantLock lock = new ReentrantLock(true);
买卖票一开始a占优,后面a b c a b c a b c均匀分布
是指多个线程按照申请锁的顺序来获取锁,这里类似排队买票,先来的人先买后来的人在队尾排着,这是公平的。
3.3、为什么会有公平锁/非公平锁的设计?为什么默认是非公平?
- 恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分的利用CPU 的时间片,尽量减少 CPU 空闲状态时间。
- 使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当1个线程请求锁获取同步状态,然后释放同步状态,因为不需要考虑是否还有前驱节点,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销。
3.4、什么时候用公平?什么时候用非公平?
如果为了更高的吞吐量,很显然非公平锁是比较合适的,因为节省很多线程切换时间,吞吐量自然就上去了; 否则那就用公平锁,大家公平使用。
4、可重入锁(又名递归锁)
4.1、可重入锁说明
可重入锁又名递归锁
是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。
如果是1个有 synchronized 修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚。
所以Java中
ReentrantLock
和synchronized
都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。

4.2、“可重入锁”详细解释
- 可:可以
- 重:再次
- 入:进入
- 锁:同步锁
- 进入什么:进入同步域(即同步代码块/方法或显示锁锁定的代码)
- 一句话:一个线程中的多个流程可以获取同一把锁,持有这把锁可以再次进入。自己可以获取自己的内部锁。
4.3、可重入锁种类
4.3.1、隐式锁Synchronized
synchronized
是java中的关键字,默认是可重入锁,即隐式锁在同步块中
public class ReSynchronizedDemo { public static void main(String[] args) { final Object objectLockA = new Object(); new Thread(() -> { synchronized (objectLockA) { System.out.println("-----外层调用"); synchronized (objectLockA) { System.out.println("-----中层调用"); synchronized (objectLockA) { System.out.println("-----内层调用"); } } } }, "a").start(); } }
image-20220709170951272 在同步方法中
public synchronized void m1() { //指的是可重复可递归调用的锁,在外层使用之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁 System.out.println(Thread.currentThread().getName() + "\t" + "-----come in m1"); m2(); System.out.println(Thread.currentThread().getName() + "\t-----end m1"); } public synchronized void m2() { System.out.println("-----m2"); m3(); } public synchronized void m3() { System.out.println("-----m3"); } @Test public void demo() { ReSynchronizedDemo reEntryLockDemo = new ReSynchronizedDemo(); reEntryLockDemo.m1(); }
Synchronized的重入实现机理
回看上方的
ObjectMoitor.hpp
140行 ObjectMonitor() { _header = NULL; _count = 0; //用来记录该线程获取锁的次数 _waiters = 0, _recursions = 0;//锁的重入次数 _object = NULL; _owner = NULL; //------最重要的----指向持有ObjectMonitor对象的线程,记录哪个线程持有了我 _WaitSet = NULL; //存放处于wait状态的线程队列 _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ;//存放处于等待锁block状态的线程队列 _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; _previous_owner_tid = 0; }
ObjectMoitor.hpp底层:每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。_count _owner
首次加锁:当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。
重入:在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么 Java 虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。
释放锁:当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。
4.3.2、显式锁Lock
- 显式锁(即Lock)也有ReentrantLock这样的可重入锁
感觉所谓的显式隐式即是指显示/隐式的调用锁
注意:
lock
unlock
要成对static Lock lock = new ReentrantLock(); @Test public void demo1() { new Thread(() -> { lock.lock(); try { System.out.println(Thread.currentThread().getName() + "\t----come in 外层调用"); lock.lock(); try { System.out.println(Thread.currentThread().getName() + "\t------come in 内层调用"); } finally { lock.unlock(); } } finally { lock.unlock(); } }, "t1").start(); }
image-20220709173125553 假如
lock
unlock
不成对,单线程情况下问题不大,但多线程下出问题public static void main(String[] args) { new Thread(() -> { lock.lock(); try { System.out.println(Thread.currentThread().getName() + "\t----come in 外层调用"); lock.lock(); try { System.out.println(Thread.currentThread().getName() + "\t------come in 内层调用"); } finally { lock.unlock(); } } finally { // lock.unlock();//-------------------------不成对|多线程情况 } }, "t1").start(); new Thread(() -> { lock.lock(); try { System.out.println("t2 ----外层调用lock"); } finally { lock.unlock(); } }, "t2").start(); }

5、死锁及排查
5.1、死锁是什么
死锁
是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。
a跟b两个资源互相请求对方的资源
死锁产生的原因
系统资源不足
进程运行推进的顺序不合适
资源分配不当
image-20220709174240932
5.2、死锁案例
public static void main(String[] args) {
Object object1 = new Object();
Object object2 = new Object();
new Thread(() -> {
synchronized (object1) {
System.out.println(Thread.currentThread().getName() + "\t 持有a锁,想获得b锁");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}//使得线程b也启动
synchronized (object2) {
System.out.println(Thread.currentThread().getName() + "\t 成功获得b锁");
}
}
}, "A").start();
new Thread(() -> {
synchronized (object2) {
System.out.println(Thread.currentThread().getName() + "\t 持有b锁,想获得a锁");
synchronized (object1) {
System.out.println(Thread.currentThread().getName() + "\t 成功获得a锁");
}
}
}, "B").start();
}

5.3、如何排查死锁
纯命令
jps -l
查看当前进程运行状况jstack 进程编号
查看该进程信息

图形化
win
+r
输入jconsole
,打开图形化工具,打开线程
,点击检测死锁
。image-20220709180146218



- 0
- 0
- 0
- 0
- 0
- 0