一、Synchronized
1、synchronized锁的分类:
1.1、方法级的同步锁
非静态同步方法锁的是当前对象
public class MyLock {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{
phone.sendEmail();
},"t1").start();
Thread.sleep(100L);
new Thread(()->{
phone.sendSMS();
},"t2").start();
/*for (int i = 0; i < 10; i++) {
new Thread(()->{
phone.sendSMS();
},Thread.currentThread().getName()+i).start();
}*/
}
}
class Phone{
public Phone(){
}
public synchronized void sendSMS(){
System.out.println(Thread.currentThread().getName()+"发送短信");
}
public synchronized void sendEmail() {
System.out.println(Thread.currentThread().getName()+"发送邮件");
try {
Thread.sleep(100000000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果: 由于phone实例对象是同一个,所以调用sendEmail后,实例对像就被加锁了,调用这个对象的其他加锁的非静态方法的时候,就会阻塞。
不同的实例对象,是不会相互影响的
类的静态方法
- 类的静态方法的锁,锁的是Class对象,所以无论是不是同一个实例,都会有锁竞争关系。
- 静态同步方法(Class对象锁)与非静态同步方法(实例对象锁)之间是不会有锁竞争的(同一实例和不同实例都一样不会有锁竞争)
1.2、代码块级的同步锁
代码级别的锁锁的就是synchronized(lock)括号中的对象
2、synchronized的底层实现:
方法级别的同步无需通过字节码来控制,它使用acc_synchronized访问标志来区分一个方法是不是同步方法,是同步方法,就获取monitor对象,执行方法,然后释放monitor对象。
方法级的同步是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中。JVM可以从方法常量池中的方法表结构(method_info_Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会 检查方法的ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有monitor(虚拟机规范中用的是管程一词), 然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放monitor。
代码级别的同步是通过monitorenter和monitorexit这两个字节码指令来实现的。
代码块的同步是利用monitorenter和monitorexit这两个字节码指令。它们分别位于同步代码块的开始和结束位置。当jvm执行到monitorenter指令时,当前线程试图获取monitor对象的所有权,如果未加锁或者已经被当前线程所持有,就把锁的计数器+1;当执行monitorexit指令时,锁计数器-1;当锁计数器为0时,该锁就被释放了。如果获取monitor对象失败,该线程则会进入阻塞状态,直到其他线程释放锁。
二、Lock
1、Lock的原理:
Lock的存储结构:一个int类型状态值(用于锁的状态变更),一个双向链表(用于存储等待中的线程)
Lock获取锁的过程:本质上是通过CAS来获取状态值修改,如果当场没获取到,会将该线程放在线程等待链表中。
Lock释放锁的过程:修改状态值,调整等待链表。 Lock大量使用CAS+自旋。因此根据CAS特性,lock建议使用在低锁冲突的情况下。
三、synchronized和Lock的区别
- Lock的加锁和解锁都是由java代码配合native方法(调用操作系统的相关方法)实现的,而synchronize的加锁和解锁的过程是由JVM管理的
- 当一个线程使用synchronize获取锁时,若锁被其他线程占用着,那么当前只能被阻塞,直到成功获取锁。而Lock则提供超时锁和可中断等更加灵活的方式,在未能获取锁的条件下提供一种退出的机制。
- 一个Lock锁内部可以有多个Condition实例,即有多路条件队列,而synchronize只有一路条件队列;同样Condition也提供灵活的阻塞方式,在未获得通知之前可以通过中断线程以 及设置等待时限等方式退出条件队列。
- synchronize对线程的同步仅提供独占模式,而Lock即可以提供独占模式,也可以提供共享模式
- synchronized是隐式锁,Lock是显式锁。
四、反编译查看monitorenter和monitorexit
原始的同步代码块的代码
/**
* 幼儿猿大班
* 2022/1/13
*/
public class RentrantLock {
public static void main(String[] args) {
synchronized ("a"){
System.out.println("外层");
synchronized ("a"){
System.out.println("中层");
}
}
}
}
反编译后的
monitorexit比monitorenter多,多一个minitorexit是为了在异常的时候保证解锁成功
D:\JavaSoftWare\jdk\jdk\jdk1.8.0_141\bin\javap.exe -c G:\project\jiaQiDemo\out\production\jiaQiDemo\com\company\lock\RentrantLock.class
Compiled from "RentrantLock.java"
public class com.company.lock.RentrantLock {
public com.company.lock.RentrantLock();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String a
2: dup
3: astore_1
4: monitorenter
5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #4 // String 外层
10: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: ldc #2 // String a
15: dup
16: astore_2
17: monitorenter
18: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
21: ldc #6 // String 中层
23: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
26: aload_2
27: monitorexit
28: goto 36
31: astore_3
32: aload_2
33: monitorexit
34: aload_3
35: athrow
36: aload_1
37: monitorexit
38: goto 48
41: astore 4
43: aload_1
44: monitorexit
45: aload 4
47: athrow
48: return
Exception table:
from to target type
18 28 31 any
31 34 31 any
5 38 41 any
41 45 41 any
}