【面试】Java并发篇(三)

0、题目大纲

三、关键字

3.1、wait、sleep、notify、notifyAll

1、sleep和wait的区别,sleep会不会释放锁
 - 追问1:sleep会不会释放锁?

2、notify和notifyAll的区别
 - 追问1:为什么notify()可能会导致死锁,而notifyAll()则不会?

3.2、Synchronized、Volatile、ReentrantLock

1、可重入锁的可重入性是什么意思,哪些是可重入锁?

2synchronized和ReentrantLock有什么区别呢?【第15讲】(*33synchronized底层如何实现?(*4)什么是锁的升级、降级?【第16讲】
 - 追问1:Synchronized对于非静态方法和静态方法加锁的区别是什么?
 - 追问2:Java 使用Synchronized关键字有什么隐患?
 - 追问3:Synchronized和lock的区别(*34、ReentrantLock底层实现原理(*25、Java读写锁,
 - 追问1:为什么并发读要加锁?

6、Volatile产生背景、作用(*2- 追问1:Volatile怎么保证可见性?
 - 追问2:Volatile怎么保证有序性?
 - 追问3:应用场景?变量不断加一,是否能够并发加正确
 - 追问4:底层如何实现?

7、Volatile和Synchronized的区别

三、关键字

3.1、wait、sleep、notify、notifyAll

1、sleep和wait的区别,sleep会不会释放锁

  • 使用方式
    sleep是线程Thread类的方法,与锁没关系。
    wait是Object顶级类方法,与notify、notifyAll一起用于锁机制。

  • 关于异常
    sleep() 必须捕获异常,wait() 不需要捕获异常。

  • CPU占有
    sleep() 释放CPU资源,不释放锁资源。
    wait()释放CPU资源,也释放锁资源。

  • 本质区别
    sleep() 是线程的运行状态控制,wait() 是线程间的通信。

追问1:sleep会不会释放锁?

[外层包有synchronized] 不会,只是释放CPU资源,锁资源并没有释放。

2、notify和notifyAll的区别

notifyAll会让 所有 等待池 的线程进入 锁池 去竞争锁的机会;
notify只会 随机选取一个 等待池的线程进入锁池去竞争锁的的机会。

补充:

1、锁池
假设线程A已经拥有了某个对象ObjectA(不是类)的锁,而其他线程B,C想要调用这个对象的某个sychronized方法(或者块)。
线程必须先获得该对象锁的拥有权,才能进入对象的的synchronized方法。
但是ObjectA的锁正被线程A拥有,所以线程B、C会被阻塞,进入一个地方【对象锁池】去等待锁释放。

简要理解,锁池就是 线程 获取锁进行等待的地方。

2、等待池
假设线程A调用了某个对象ObjectA的wait方法,线程A就会释放对象的锁;同时线程A就进入ObjectA的等待池,进入等待池中的线程不会去竞争ObjectA的锁

重点:锁池和等待池都是针对对象。
追问1:为什么notify()可能会导致死锁,而notifyAll()则不会?

notifyAll会将全部线程由等待池移到锁池参与锁竞争,成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争;而notify只会唤醒一个线程,所以可能导致死锁。

3.2、Synchronized、Volatile、ReentrantLock

Java 内存模型(JMM) 作用于工作内存(本地内存)和主存之间数据同步过程,它规定了如何做数据同步以及什么时候做数据同步,如下图。
4

1、可重入锁的可重入性是什么意思,哪些是可重入锁?

可重入性:可以正确重复使用。
可重入锁:synchronized 和 ReentrantLock。

(待消化确认)可重入锁:自己可以再次获取自己的内部锁。比如,一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁时,还可以再获取的;如果不可锁重入的话,就会造成死锁;同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时,才能最终释放锁。

2、synchronized和ReentrantLock有什么区别呢?【第15讲】(*3)

【1】相同:两者都是可重入锁,都提供互斥语义,当一个线程已经获取当前锁时,其他试图获取的线程只能等待或者阻塞在那里。

【2】区别

  • synchronized 依赖于 JVM,而 ReentrantLock 依赖于 API
  • ReentrantLock 增加了三点功能:等待可中断、可实现公平锁、可实现选择性通知(锁可以绑定多个条件)

【3】性能:不能一概而论,synchronized早期性能相差较大,后续版本有较多改进,在低竞争场景中表现可能优于 ReentrantLock。

补充:
0、Lock接口比synchronized锁定操作更广泛、更灵活。可以具有完全不同的属性,并且可以支持多个关联的Condition条件对象。
1、ReentrantLock锁比synchronized更具扩展性,灵活高效,额外具有嗅探锁定、多路分支通知等功能;
2、Lock接口提供比synchronized更广泛与灵活的操作;
3、Condition给ReentrantLock提供了等待/通知,而且相比synchronized中函数(wait,notify,notifyAll)更加灵活性,还可以实现多路通知功能,

3、synchronized底层如何实现?(*4)什么是锁的升级、降级?【第16讲】

synchronized 代码块是由一对儿 monitorenter/monitorexit 指令实现的,Monitor 对象是同步的基本实现单元。

在 Java 6 之前,Monitor 的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作。

现代的(Oracle)JDK 中,JVM 对此进行了大刀阔斧地改进,提供了三种不同的 Monitor 实现,也就是常说的三种不同的锁:偏斜锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能。

所谓锁的升级、降级,就是 JVM 优化 synchronized 运行的机制,当 JVM 检测到不同的竞争状况时,会自动切换到适合的锁实现,这种切换就是锁的升级、降级。

当没有竞争出现时,默认会使用偏斜锁。JVM 会利用 CAS 操作(compare and swap),在对象头上的 Mark Word 部分设置线程 ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁。这样做的假设是基于在很多应用场景中,大部分对象生命周期中最多会被一个线程锁定,使用偏斜锁可以降低无竞争开销。

如果有另外的线程试图锁定某个已经被偏斜过的对象,JVM 就需要撤销(revoke)偏斜锁,并切换到轻量级锁实现。轻量级锁依赖 CAS 操作 Mark Word 来试图获取锁,如果重试成功,就使用普通的轻量级锁;否则,进一步升级为重量级锁。

我注意到有的观点认为 Java 不会进行锁降级。实际上据我所知,锁降级确实是会发生的,当 JVM 进入安全点(SafePoint)的时候,会检查是否有闲置的 Monitor,然后试图进行降级。

追问1:Synchronized对于非静态方法和静态方法加锁的区别是什么?

1)修饰非静态方法,实际是对调用该方法的对象加锁,俗称“对象锁”。
2)修饰静态方法, 实际是对该类对象加锁,俗称“类锁”。

同步非静态方法,实际上锁定的是当前对象的实例对象。在同一时刻只有一个线程可以访问该实例的同步方法,但对于多个实例的同步方法,不同实例之间对同步方法的访问是不受同步影响(synchronized 同步失效)。

同步静态方法,锁的是类对象而不是某个实例对象,所以可以理解为对于静态方法的锁是全局的锁,同步也是全局的同步。

synchronized修饰对象 非静态方法 静态方法
case 1 同一个对象 在两个线程中 分别访问该对象的两个同步方法 用类直接调用两个不同的同步方法
result 互斥 互斥
reason 对象锁,当对象调用一个synchronized时,其他同步方法要等其执行完才释放锁,然后执行。 类(.class)锁,类对象只有一个,只有一个空间使用。可理解为里面有N个房间,一把锁,因此房间(同步方法)之间一定是互斥的。
case 2 不同对象在两个线程中调用同一个同步方法 用一个类的静态对象调用静态方法或非静态方法
result 不互斥 互斥
reason 对象锁,这里是两个对象。其中方法可并发执行,不会互斥。形象说就是每个线程调用方法时会new一个对象,那么就出现两个空间,两把钥匙 因为是一个对象调用,同上
case 3 - 一个对象在两个线程中分别调用一个静态同步方法和一个非静态同步方法
result - 互斥
reason - 虽然是一个对象调用,但两个方法的锁类型不同,调用的静态方法是类对象在调用,即这两个方法产生的并不是同一个对象锁,因此不会互斥,会并发执行
追问2:Java 使用Synchronized关键字有什么隐患?
  • 1)不能设置锁超时时间;
  • 2)不能通过代码释放锁;
  • 3)容易造成死锁。
追问3:Synchronized和lock的区别(*3)
锁类型 synchronized lock
性能 资源竞争不激烈时,synchronize性能更好。 资源竞争激烈时,lock性能更好
锁机制 在JVM层面实现,系统会监控锁的释放与否 是JDK代码实现,需要手动释放资源
编程 synchronized更简洁,可以用在方法/代码块 lock功能多,更灵活,缺点是只能写在代码里,不能直接修改代码
延伸:synchronize会根据锁竞争情况,从偏向锁-->轻量级锁-->重量级锁升级,lock可以采用非阻塞的方式获取锁。

4、ReentrantLock底层实现原理(*2)

4
ReentrantLock通过重写锁获取方式和锁释放方式这两个方法实现了公平锁和非公平锁。

首先ReentrantLock继承自父类Lock,然后有3个内部类,其中Sync内部类继承自AQS,另外的两个内部类继承自Sync,这两个类分别是用来公平锁和非公平锁的。

通过Sync重写的方法tryAcquire、tryRelease可以知道,ReentrantLock实现的是AQS的独占模式,也就是独占锁,这个锁是悲观锁。
……

5、Java读写锁,

与传统锁不同的是读写锁的规则是可以共享读,但只能一个写,总结起来为:读读不互斥,读写互斥,写写互斥,而一般的独占锁是:读读互斥,读写互斥,写写互斥,而场景中往往读远远大于写,读写锁就是为了这种优化而创建出来的一种机制。注意是读远远大于写,一般情况下独占锁的效率低来源于高并发下对临界区的激烈竞争导致线程上下文切换。因此当并发不是很高的情况下,读写锁由于需要额外维护读锁的状态,可能还不如独占锁的效率高。因此需要根据实际情况选择使用。

追问1:为什么并发读要加锁?

读锁是防止读到写的中间值。

6、Volatile产生背景、作用(*2)

背景:处理器和内存间的多级缓存提升了处理器的执行速度,但也引入缓存数据不一致的问题。而多线程访问同一个变量,一个线程修改变量值,很可能导致其他线程读不一致。

作用:volatile是一个变量修饰符,只能用来修饰变量,无法修饰方法及代码块等。
用法:简单,只需用volatile修饰可能被多线程同时访问的变量即可。

补充:
1、缓存一致性背景:多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议
2、缓存一致性协议:每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。
追问1:Volatile怎么保证可见性?

可见性:当多线程访问同一变量时,一线程修改这个变量值,其他线程能立即看到修改值。

volatile可以将被修饰变量被修改后立即同步到主内存,变量在每次用之前都从主内存刷新。因此可以保证多线程操作时变量可见性。

变量被volatile所修饰的话,在每次数据变化之后,其值都会被强制刷入主存。而其他处理器的缓存由于遵守了缓存一致性协议,也会把这个变量的值从主存加载到自己的缓存中。

追问2:Volatile怎么保证有序性?

volatile可以禁止指令重排,保证了代码严格按照先后顺序执行。这就保证了有序性。

原子性:一个或一组操作要么都做,要么都不做。volatile不能保证原子性。
追问3:应用场景?变量不断加一,是否能够并发加正确

应用场景:一个变量的值更新时不依赖于当前值,且该变量不会和其他一起构成一个不可变条件。

例如:
1、可用volatile
1-1)只有一个线程执行写操作,其它线程都是读操作;
1-2)只有简单读写操作,如set i=2, get i。

2、不能用volatile
i++就不行,它有一次读一次写
追问4:底层如何实现?

内存屏障……

7、Volatile和Synchronized的区别

关键字 volatile synchronized
使用级别 仅能修饰变量 可修饰变量、方法、和类
要素保证 仅保证可见性、有序性 可保证可见性、有序性、原子性
阻塞 不会造成线程阻塞 可能会
编译器优化 标记变量不会被优化 标记变量可以被优化
  • 本质对比
  • volatile本质是告诉JVM当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;
  • synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。

四、参考

1、Java中sleep()与wait()区别
2、Java多线程sleep和wait的区别,总结得非常好。
3、sleep、yield、wait、join的区别(阿里)
4、notify,notifyAll区别(生产者消费者案例)
5、notify和notifyAll的区别
6、java中的notify和notifyAll有什么区别?
7、java中的notify和notifyAll有什么区别?
8、深入理解可重入性
9、Synchronized同步静态方法和非静态方法总结
10、谈谈 synchronized 和 ReentrantLock 的区别
11、ReenTrantLock可重入锁(和synchronized的区别)总结
12、java的两种同步方式, Synchronized与ReentrantLock的区别
13、Java synchronized 多线程同步问题详解
14、面试官:说说多线程并发问题
15、深入分析Synchronized原理(阿里面试题)
16、Synchronized和Lock的区别
17、深入理解ReentrantLock的实现原理
18、ReentrantLock 实现原理
19、Java–读写锁的实现原理
20、读锁有什么用?读为什么要加锁?
21、Java并发编程原理与实战十八:读写锁
22、深入理解Java中的volatile关键字
23、一个volatile跟面试官扯了半个小时
24、volatile与synchronized的区别

猜你喜欢

转载自blog.csdn.net/HeavenDan/article/details/112984259