专栏系列文章地址:https://blog.csdn.net/qq_26437925/article/details/145290162
本文目标:
- 关于synchronized已经有很多博主很细致的讲解了,本文主要是通过代码例子再次理解下,并要求能头脑反射出相关知识点
- 主要是锁分类的知识点掌握,方便平时工作中知晓用什么锁,细节则需要专门深入
目录
- synchronized
- 锁住什么
- 保证线程安全
- 锁升级
- 相关题目
- 附加测试代码,请每个case过一下
- 锁分类
synchronized
提及synchronized需要想到如下的几个知识点
锁住什么
是对象,参考阅读ObjectMonitor相关知识:https://blog.csdn.net/qq_26437925/article/details/145400968
补充Java对象头之markword
markword共8个字节,64bit,包括:锁信息,gc信息,identity hashcode
保证线程安全
即原子性、可见性、有序性
-
synchronized
提供了一种锁对机制,能确保共享变量的互斥访问,从而防止数据不一致问题的出现 -
synchronized
包括了monitorenter
和monitorexit
两个JVM指令(jvm层面则是C/C++调用了操作系统提供的同步机制,其是要依赖于硬件cpu的。CPU级别则是使用lock指令来实现的):它能确保在任何时候,任何线程执行到monitor enter
成功之前都必须从主内存
中获取数据,而不是从缓存
中,在monitor exit
运行成功之后,共享变量被更新后的值必须刷入主内存
内 -
synchronized
严格准守Java happends-before
规则,一个monitor exit
指令之前必定要有一个monitor enter
反编译查看(字节码层级的实现)
java">public class SynchronizedDemo {
public void method (){
synchronized (this) {
System.out.println("method 1 start!!!!");
}
}
}
java">javac -encoding utf-8 SynchronizedDemo.java
javap -c SynchronizedDemo
java">Compiled from "SynchronizedDemo.java"
public class SynchronizedDemo {
public SynchronizedDemo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void method();
Code:
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #3 // String method 1 start!!!!
9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: aload_1
13: monitorexit
14: goto 22
17: astore_2
18: aload_1
19: monitorexit
20: aload_2
21: athrow
22: return
Exception table:
from to target type
4 14 17 any
17 20 17 any
}
参考:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-3.html#jvms-3.14
锁升级
java"> 【偏向锁】(匿名对象)
/ \
对象new出来(无锁) \
\ \(只要有线程竞争,就会偏向-->轻量)
\ \
\ \
\ \
【轻量级锁】
\
\
\
【重量级锁】
偏向锁默认启动,会延迟启动(普通对象,有了偏向锁就是个匿名偏向)
轻量级锁,自旋锁,无锁:指向线程栈中Lock Record
的指针(CAS操作)
重量级锁:操作系统层面,有竞争队列,等待队列(wait_set),不需要消耗CPU,后续操作系统调度
偏向锁 什么时候升级为 轻量级锁?
答:只要有线程竞争
轻量级锁 什么时候升级为 重量级锁?
答:JDK1.6之前:自旋次数10次;或者多个线程等待(超过CPU核心数的1/2) 就会发生升级;目前是JVM自适应自旋的升级
-
轻量级锁:消耗CPU(用户态,不经过操作系统)
-
重量级锁:不消耗CPU,有一个等待队列(阻塞); 涉及到用户态/内核态切换
相关题目
参考博主:橡 皮 人的一篇博文
https://blog.csdn.net/weixin_45433817/article/details/132216383
下面进行测试和一一说明,原文讲解的正确,但测试代码不友好
- 两个都是同步方法,先打印邮件还是短信?-------------先邮件再短信,共用一个对象锁。
例子代码:
java">import java.util.concurrent.TimeUnit;
class Phone1 {
public synchronized void sendEmail() {
System.out.println("------sendEmail");
}
public synchronized void sendSMS() {
System.out.println("------sendSMS");
}
}
public class Main {
public static void main(String[] args) throws Exception {
case1();
}
public static void case1() {
Phone1 phone = new Phone1();
new Thread(() -> {
phone.sendEmail();
}, "a").start();
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.sendSMS();
}, "b").start();
}
}
这种情况是锁住对象实例,先执行的先获取到锁
- sendEmail()休眠3秒,先打印邮件还是短信?----------先邮件再短信,共用一个对象锁。
java">class Phone2 {
public synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------sendEmail");
}
public synchronized void sendSMS() {
System.out.println("------sendSMS");
}
}
java">public static void case2() {
Phone2 phone = new Phone2();
new Thread(() -> {
phone.sendEmail();
}, "a").start();
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.sendSMS();
}, "b").start();
}
仍然是对象锁,先获取到锁的先执行。虽然有sleep,但是sleep不会让出锁。所以会看先先停顿一会打印sendEmail, 然后是sendSms
- 添加一个普通的hello方法,先打印普通方法还是邮件?------先hello,再邮件。
这种自不必说,一个有锁一个无锁,无竞争关系,谁先执行到打印谁
-
两部手机,一个发短信,一个发邮件,先打印邮件还是短信?----先短信后邮件 资源没有争抢,不是同一个对象锁。
不同对象实例,各种的对象锁,所以a,b线程没有竞争关系, 虽然sendEmail先执行的但是有sleep后打印的 -
两个静态同步方法,一部手机,先打印邮件还是短信?-----先邮件再短信,共用一个类锁。
java">class Phone3 {
public synchronized static void sendEmail() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
}
System.out.println("------sendEmail");
}
public synchronized static void sendSMS() {
System.out.println("------sendSMS");
}
}
static方法用的是class对象锁,所以是有竞争关系的,先先获取到锁谁先执行,sleep不影响锁逻辑
- 两个静态同步,两部手机,一个发短信,一个发邮件,先打印邮件还是短信?-----先邮件后短信,共用一个类锁。
同5的解释
- 邮件静态同步,短信普通同步,先打印邮件还是短信?—先短信再邮件,一个类锁一个对象锁。
- 邮件静态同步,短信普通同步,两部手机,先打印邮件还是短信?------先短信后邮件,一个类锁一个对象锁。
java">class Phone5 {
public synchronized static void sendEmail() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
}
System.out.println("------sendEmail");
}
public synchronized void sendSMS() {
System.out.println("------sendSMS");
}
}
- 静态方法用class对象的对象锁,普通方法用对象实例的对象锁
- 二者没有竞争关系,谁先执行到打印方法就谁先打印
附加测试代码,请每个case过一下
java">import java.util.concurrent.TimeUnit;
class Phone1 {
public synchronized void sendEmail() {
System.out.println("------sendEmail");
}
public synchronized void sendSMS() {
System.out.println("------sendSMS");
}
public void hello() {
System.out.println("------hello");
}
}
class Phone2 {
public synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
}
System.out.println("------sendEmail");
}
public synchronized void sendSMS() {
System.out.println("------sendSMS");
}
}
class Phone3 {
public synchronized static void sendEmail() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
}
System.out.println("------sendEmail");
}
public synchronized static void sendSMS() {
System.out.println("------sendSMS");
}
}
class Phone4 {
public synchronized static void sendEmail() {
System.out.println("------sendEmail");
}
public synchronized void sendSMS() {
System.out.println("------sendSMS");
}
}
class Phone5 {
public synchronized static void sendEmail() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
}
System.out.println("------sendEmail");
}
public synchronized void sendSMS() {
System.out.println("------sendSMS");
}
}
public class Main {
public static void main(String[] args) throws Exception {
case6();
}
public static void case6() {
Phone5 phone = new Phone5();
new Thread(() -> { phone.sendEmail(); }, "a").start();
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> { phone.sendSMS(); }, "b").start();
}
public static void case5() {
Phone4 phone = new Phone4();
new Thread(() -> { phone.sendEmail(); }, "a").start();
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> { phone.sendSMS(); }, "b").start();
}
public static void case4() {
Phone3 phone = new Phone3();
new Thread(() -> { phone.sendEmail(); }, "a").start();
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> { phone.sendSMS(); }, "b").start();
}
public static void case3() {
Phone2 phone21 = new Phone2();
Phone2 phone22 = new Phone2();
new Thread(() -> { phone21.sendEmail(); }, "a").start();
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> { phone22.sendSMS(); }, "b").start();
}
public static void case2() {
Phone2 phone = new Phone2();
new Thread(() -> {
phone.sendEmail();
}, "a").start();
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.sendSMS();
}, "b").start();
}
public static void case1() {
Phone1 phone = new Phone1();
new Thread(() -> {
phone.sendEmail();
}, "a").start();
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.sendSMS();
}, "b").start();
}
}
锁分类
以下是可以看到的一些锁的概念:
- 可重入锁、不可重入锁
- 乐观锁、悲观锁
- 公平锁、非公平锁
- 共享锁、互斥锁(或拍它锁)
- 无锁、偏向锁、轻量级锁、重量级锁
- 自旋锁,自适应自旋锁
- 读写锁;写锁、悲观读锁和乐观读锁
- 分段锁(Striped Locking)
可参考腾讯云文章:https://cloud.tencent.com/developer/news/1847413