【Java进阶营】java面试题---并发①

1. 什么叫死锁?

    死锁就是多个线程对自己持有的资源不释放,同时又去申请对方持有的资源,形成循环等待。

2. 什么是乐观锁?什么是悲观锁?

    乐观锁就是很乐观,认为别人不会修改它的数据,不会上锁,每次操作前会判断别人有没有改过它的数据,CAS 就属于乐观锁;
    悲观锁就是很悲观,认为别人一定会改它的数据,每次操作都会上锁,Synchronized 和 ReentrantLock 都是悲观锁。

3. 什么是自旋锁?适应性自旋锁呢

    阻塞和唤醒线程需要系统切换CPU状态,这个开销是比较大的。如果一段代码的执行时间很短,刚阻塞线程,代码就执行完了,接着又唤醒线程,这种方式效率不高,倒不如让线程不阻塞,等代码执行完。那么就可以让线程自旋,不必阻塞,等前面线程执行完它就可以获取资源,这就是自旋锁。实现原理就是 CAS。适应性自旋锁就是虚拟机会判断自旋获取到锁的概率大不大,如果大,就自旋,如果不大,就阻塞线程。

4. 什么是共享锁?什么是独占锁?

    共享锁就是可以同时被多个线程持有,一个资源被加上共享锁后,那么其他线程也只能对其加共享锁。获得共享锁的线程只能读数据,不能写数据。ReentrantReadWriteLock 的读锁是共享锁,写锁是独占锁。
    独占锁又叫排他锁,同一时刻只能被一个线程持有,获取到锁的线程可以对资源进行读写操作。Synchronized 和 ReentrantLock 就是独占锁。

5. 什么是可重入锁?

    可重入锁又叫递归锁,就是一个线程获取到锁后,就可以进入它同步着的所有代码,即使内层函数也被锁住,也无需重新获取锁,Synchronized 和 ReentrantLock 都是可重入锁。

6. 可重入锁的原理是什么?

    有个 state 变量,线程获取到锁就加 1,释放锁就减 1,线程获取锁的时候会判断 state 是不是 0,是 0 就让线程获取锁,state 加 1,如果不是 0,但是当前持有锁的线程等于当前线程,state 也会加 1。

7. 公平锁和非公平锁有什么区别?

    公平锁就是按照申请锁的顺序去获取锁,先来后到,而非公平锁并不会按照顺序,在高并发的情况下可能出现优先级反转和饥饿现象,就是优先级高的反而后获取到锁,或者某些线程一直没有获取到锁。Synchronized 就是非公平锁,ReentrantLock 可以默认非公平锁,可以通过参数设置为公平锁。

8. CAS 是什么,会有什么问题吗?怎么解决?

    CAS 就是比较并交换,它有三个操作数,内存值,期望值,更新值。当且仅当内存值等于期望值时,才会把内存值修改为更新值,它以自旋的方式同步线程,减少了线程切换的开销。它会出现 ABA 问题,就是线程 1 将共享变量 A 改为 B,再改为 A,线程 2 去判断的时候,以为没有别的线程改过,解决办法是可以每次操作都加个版本号。还有个问题就是 CAS 只能保证一个变量的原子操作,可以用原子引用来解决。如果 CAS 长时间不成功,就会一直自旋,占用大量的 CPU,可以加次数限制。CAS 底层是通过内存偏移量来获取内存值的。

9. 说说你对 AQS 的理解?

    AQS,又叫抽象队列同步器,它是 JUC 的基石。JUC 包下的锁、并发工具,都有一些相似的代码,然后把这些代码抽取出来,就是 AQS,也即 JUC 包下的都是基于 AQS 去构建的。

10. AQS 是怎么协调工作的?

    AQS 底层是通过 LockSupport 实现的。LockSupport 是等待唤醒的另一种实现方式,它使用一个通行证的概念。每个线程都有一个通行证,通行证的数量只可能为 0 或者 1,默认是 0。调用 unpark 方法的时候会发放一个通行证,线程就会被唤醒;调用 park 方法的时候,就会消耗一个通行证,线程就会被阻塞。park 和 unpark 方法底层是调用了 unsafe 类的 native 方法。

11. AQS 的工作原理是什么?

    有个 volatile 修饰的 int 类型的 state 变量,用来表示同步状态,将线程封装成 node 节点,通过内置的队列来完成资源的获取和排队工作,用 CAS 来完成对状态的修改。

12. 线程申请资源的时候,AQS 是怎么进行入队和出队工作的?

    入队:假如现在线程 A 正持有锁,此时线程 B 想获取锁,就要进入到队列中等待。如果队列还是空的,首先会创建一个节点,称为傀儡节点,然后把队列的 head 指针和 tail 指针都指向它,然后把线程 B 封装成一个节点,然后把这个节点的 prev 指向傀儡节点,把傀儡节点的 next 指向该节点,最后把 tail 指针指向该节点。
    出队:假如现在线程 A 释放锁了,那么线程 B 对应的节点就应该出队。首先会把 head 指向线程 B 对应的节点,然后把线程 B 对应节点的线程设置为空,接着把该节点的 prev 设置为空,把傀儡节点的 next 设置为空,这样一来,原先线程 B 所在的节点就成了新的傀儡节点,原先的傀儡节点就没有任何引用指向它,就会被 GC 回收。在此我向大家推荐一个架构学习交流圈。交流学习指导伪鑫:1253431195(里面有大量的面试题及答案)里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

13. 你知道哪些并发关键字?

    synchronized,volatile、final。

14. 说说你对 synchronized 的理解?

    它是一个关键字,是可重入的非公平锁。可以修饰实例方法、静态方法和代码块。修饰实例方法时锁对象是实例,修饰静态方法时锁对象是当前类,修饰代码块时锁对象可以任意对象。修饰方法时,通过 javap 命令反汇编可以看到它是通过 ACC_SYNCHRONIZED 标识来实现同步的;而修饰代码块时是通过 monitor 对象来实现同步的,monitorenter 指向锁开始的地方,monitorexist 指向锁退出的地方,并且有两个 monitorexist,是为了防止程序异常导致锁未释放。

15. 锁状态有哪些?

无锁、偏向锁、轻量级锁、重量级锁。

    无锁就是不阻塞线程,在循环内不断地尝试,CAS 便是无锁的实现;
    偏向锁就是在锁对象头里会保存当前持锁的线程 ID,如果申请资源的线程 ID 等于对象头里保存的线程 ID,那就直接让线程获取锁;
    轻量级锁就是当锁是偏向锁时别的线程进来请求资源了,那就会自旋一定次数去尝试获取锁,自旋一定次数没获取到,又或者是一个线程持有锁,一个在自旋,第三个线程进来了,都会升级为重量级锁;
    重量级锁就是等待锁的线程都会阻塞。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值