java锁synchronized面试常问总结
作者:王有志 时间:2023-08-01 05:11:37
synchronized都问啥?
如果Java面试有什么是必问的,synchronized
必定占据一席之地。初出茅庐时synchronized
的用法,成长后synchronized
的原理,可谓是Java工程师的“一生之敌”。
按照惯例,先来看synchronized
的常见问题(在线Excel同步更新中):
根据统计数据可以总结出synchronized
的5大考点:
synchronized的使用方式:
synchronized是什么?
synchronized怎么用?
不同用法都有什么效果?
synchronized的实现原理:
synchronized的特性是如何实现的?
synchronized锁升级的原理。
今天我们先来看synchronized
的基础部分。
synchronized是什么?
synchronized
是Java中的关键字,提供了原生同步机制,实现互斥语义和可见性保证,通常称为互斥锁。
互斥指的是,当线程获取到锁后,其它试图获取锁的线程只能阻塞;
可见性指的是,
synchronized
修饰的语句内修改共享变量可以立即被其它线程获取。
互斥就意味着,同一时间只有一个线程执行synchronized
修饰的代码,那么:
无论怎么重排序,都会遵循as-if-serial语义,因此
synchronized
中不存在有序性问题;不主动释放锁,其他线程无法执行
synchronized
中代码,无需考虑原子性问题。
因此synchronized
中互斥就代表了对有序性问题和原子性问题的保证。不过前提是JSR-133中反复提到的correctly synchronized(正确的同步),举个例子:
public class IncorrectlySynchronized {
private Integer count = 0;
public void add() {
synchronized (count) {
count++;
}
}
public static void main(String[] args) throws InterruptedException {
IncorrectlySynchronized incorrectlySynchronized = new IncorrectlySynchronized();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
incorrectlySynchronized.add();
}
});
Thread t2 = new Thread(()-> {
for (int i = 0; i < 10000; i++) {
incorrectlySynchronized.add();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(incorrectlySynchronized.count);
}
}
看似该加synchronized
的地方都加了,但是结果却会出乎意料,这就典型的错误同步的例子。
synchronized锁什么?
既然是锁,那么synchronized
锁的是什么呢?
《The Java Language Specification》中描述(节选)到:
Each object in Java is associated with a monitor, which a thread can lock or unlock. The synchronized statement computes a reference to an object; it then attempts to perform a lock action on that object's monitor and does not proceed further until the lock action has successfully completed.
Java中每个对象都与一个监视器关联,线程可以锁定或者解锁该监视器。synchronized
语句尝试锁定与对象关联的监视器,锁定成功后才可以继续执行。
通常,我们将synchronized
锁定与对象关联的监视器理解为synchronized
锁定对象本身。
在我们知道synchronized
锁什么后,再去看用法,很多内容就会一目了然了。
synchronized怎么用?
作为关键字,synchronized
有两种用法:
修饰代码块
修饰方法
修饰成员方法
修饰静态方法
之前有个同事特别迷信“背技术”,为了区分不同用法的效果,背了某机构的“线程八锁”,但每过一段时间就会忘记。
其实,知道了synchronized
锁什么,不同用法的效果自然就出来了,看一个例子:
public class SynchronizedDemo {
public static void main(String[] args) throws InterruptedException {
SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
Thread t1 = new Thread(synchronizedDemo::lockMemberMethod1);
Thread t2 = new Thread(synchronizedDemo::lockMemberMethod2);
t1.start();
// 确保t1先执行
TimeUnit.SECONDS.sleep(1);
t2.start();
}
private synchronized void lockMemberMethod1() {
System.out.println("方法1");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private synchronized void lockMemberMethod2() {
System.out.println("方法2");
}
}
通过实例变量调用成员方法时,会隐式的传递this
。这个例子中,t1和t2想锁定的监视器是谁的?synchronizedDemo
对象的。t1先获取到,那么t2只能等待t1释放后再获取了。
那此时的锁定范围是什么?synchronizedDemo
对象。
修改下代码:
public static void main(String[] args) throws InterruptedException {
SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
SynchronizedDemo synchronizedDemo2 = new SynchronizedDemo();
Thread t1 = new Thread(synchronizedDemo::lockMemberMethod1);
Thread t2 = new Thread(synchronizedDemo2::lockMemberMethod2);
t1.start();
t2.start();
}
t2不再争夺synchronizedDemo
而是争夺synchronizedDemo2
,结果上也能看出t1和t2之间不存在竞争关系。
那么使用synchronized
修饰静态方法和代码块是什么效果呢?
private static synchronized void lockStaticMethod() {
System.out.println("静态方法!");
}
private void lockCodeBlock(int count) {
synchronized (this) {
System.out.println("成员方法的代码块!");
}
}
使用synchronized
修饰静态方法,锁定的对象是SynchronizedDemo.class
。所有SynchronizedDemo
的实例对象共用同一个SynchronizedDemo.class
,同一时间不同变量,只有一个线程可以执行lockStaticMethod
方法。
至于synchronized
修饰代码块,就比较灵活了,括号中是谁就锁定谁。如果是this
就锁定实例变量,如果是SynchronizedDemo.class
效果就和修饰静态方法一样。
至于前面错误的同步的例子,它的问题是count
对象在不断变化(Integer
实现相关)的,因此synchronized
锁定的并不是同一个对象。
来源:https://juejin.cn/post/7173910949488197663
![](/images/zang.png)
![](/images/jiucuo.png)
猜你喜欢
java 查找字符串所在的位置代码
![](https://img.aspxhome.com/file/2023/0/79820_0s.jpg)
Java负载均衡算法实现之轮询和加权轮询
Spring配置shiro时自定义Realm中属性无法使用注解注入的解决办法
![](https://img.aspxhome.com/file/2023/2/83122_0s.jpg)
三行Android代码实现白天夜间模式流畅切换
![](https://img.aspxhome.com/file/2023/7/139117_0s.gif)
Java中五种不同方法的创建对象
Android自定义EditText实现淘宝登录功能
![](https://img.aspxhome.com/file/2023/6/139656_0s.png)
Java使用JSON传递字符串注意事项解析
![](https://img.aspxhome.com/file/2023/7/84277_0s.png)
java开发线上事故理解RocketMQ异步精髓
![](https://img.aspxhome.com/file/2023/1/57501_0s.jpg)
c# 模拟串口通信 SerialPort的实现示例
![](https://img.aspxhome.com/file/2023/3/87023_0s.png)
Struts2中validate数据校验的两种方法详解附Struts2常用校验器
![](https://img.aspxhome.com/file/2023/4/96604_0s.png)
mybatis中mapper-locations的作用
![](https://img.aspxhome.com/file/2023/0/59350_0s.png)
Java中自动装箱、拆箱引起的耗时详解
超详细的Intellij IDEA 看源码必备技能
![](https://img.aspxhome.com/file/2023/2/69312_0s.png)
使用maven运行Java Main的三种方法解析
一文详解Java中流程控制语句
Spring代理对象导致的获取不到原生对象注解的解决
Java使用设计模式中的工厂方法模式实例解析
![](https://img.aspxhome.com/file/2023/4/61054_0s.jpg)
JDK14性能管理工具之jstack使用介绍
SpringBoot 添加本地 jar 文件的操作步骤
![](https://img.aspxhome.com/file/2023/8/88178_0s.jpg)
详解Java volatile 内存屏障底层原理语义
![](https://img.aspxhome.com/file/2023/5/116225_0s.png)