Sychronized和ReentrantLock锁 面试题

时间:2024-03-06 13:57:24

Sychronized和ReentrantLock锁 面试题

  • 前言
  • 1、Java死锁如何避免?
  • 2、公平锁和⾮公平锁的底层实现?
  • 3、ReentrantLock中tryLock()和lock()⽅法的区别?
  • 4、Sychronized的偏向锁、轻量级锁、重量级锁?
  • 5、谈谈你对AQS的理解,AQS如何实现可重⼊锁?
  • 6、AQS锁的类别?
  • 7、CountDownLatch和Semaphore的区别和底层原理?
  • 8、ReentrantLock底层原理?
  • 9、Sychronized和ReentrantLock的区别?
  • 10、Synchronized 偏向锁有个开关,如果默认开启有什么缺点?
  • 11、CAS与传统synchronized区别
  • 12、CAS是不是操作系统执行的?
  • 13、说一下CAS怎么用的,会有哪些问题?
  • 14、cas自增 和 Synchronized 自增 谁快?
  • 15、Synchronized 和 ReentracLock 哪个快,为啥?
  • 16、怎么检测一个线程是否拥有锁?
  • 17、事务未提交而锁提前释放了?
  • 18、AtomicInteger计数器自增到一万以后,怎么归零?
  • GIT
  • 19、什么是git?
  • 20、列举工作中常用的git命令:
  • 总结


前言

最新的 Java 面试题,技术栈涉及 Java 基础、集合、多线程、Mysql、分布式、Spring全家桶、MyBatis、Dubbo、缓存、消息队列、Linux…等等,会持续更新。

如果对老铁有帮助,帮忙免费点个赞,谢谢你的发财手!

1、Java死锁如何避免?

造成死锁的4个必要条件:

  • 1.⼀个线程每次只能占有⼀个资源;
  • 2.⼀个线程在阻塞等待某个资源时,不释放已占有资源;
  • 3.⼀个线程已占有的资源,在未使⽤完之前,不能被强⾏剥夺;
  • 4.多个线程形成循环等待的关系。
    如果要避免死锁,只需要其中某⼀个条件不满⾜即可。
    在开发过程中:
  • 1.要注意加锁顺序,保证每个线程按同样的顺序进⾏加锁解锁;
  • 2.要注意加锁时限,可以针对锁设置⼀个超时时间;
  • 3.要注意死锁检查,这是⼀种预防机制,确保在第⼀时间发现死锁并进⾏解决。

2、公平锁和⾮公平锁的底层实现?

公平锁和⾮公平锁,它们的底层实现都会使⽤AQS来进⾏排队。
它们的区别在于:

  • 如果是公平锁,会先检查AQS队列中是否存在线程在排队,如果有线程在排队,则当前线程也进⾏排队。
  • 如果是⾮公平锁,则不会去AQS队列中检查是否有线程在排队,⽽是直接竞争锁。
    不管是公平锁还是⾮公平锁,⼀旦没竞争到锁,都会进⾏排队,当锁释放时,都是唤醒排在最前⾯的线程,所以⾮公平锁只是体现在了线程加锁阶段,而没有体现在线程唤醒阶段。
    另外,不管是公平锁还是⾮公平锁都是可重⼊的。

3、ReentrantLock中tryLock()和lock()⽅法的区别?

  • 1.tryLock()表示尝试加锁,可能加到,也可能加不到,该⽅法不会阻塞线程,如果加到锁则返回true,没有加到则返回false ;
  • 2.lock()表示阻塞加锁,线程会阻塞直到加到锁,⽅法也没有返回值。

4、Sychronized的偏向锁、轻量级锁、重量级锁?

  • 1、偏向锁:在锁对象的对象头中记录了当前获取到锁的线程ID,如果该线程⼜来获取锁,就可以直接获取到了,这就是可重入的概念;
  • 2、轻量级锁:如果有另外的线程来竞争锁,偏向锁就会升级为轻量级锁,轻量级锁的底层是通过⾃旋来实现的,并不会阻塞线程;
  • 3、重量级锁:如果⾃旋次数超过10次仍然没有获取到锁,则会升级为重量级锁,重量级锁会导致线程阻塞。
  • 4、⾃旋锁:⾃旋就是线程在获取锁的过程中,不会去阻塞线程,是线程通过CAS获取预期的⼀个标记,如果没有获取到,则继续循环获取,如果获取到了则表示获取到了锁,这个过程线程⼀直在运⾏中,消耗的CPU资源比较少,⽐较轻量。

5、谈谈你对AQS的理解,AQS如何实现可重⼊锁?

  • 1.AQS是⼀个抽象的对列同步器,在AQS中,维护了一个volatile修饰的state标识和⼀个双向链表队列(FIFO);
  • 2.这个队列就是⽤来给线程排队的,⽽state就像是⼀个红绿灯,⽤来控制线程排队或者放⾏的;
  • 3.在可重⼊锁的场景下,state就⽤来表示加锁的次数,0标识⽆锁,每加⼀次锁,state就加1,释放锁state就减1。

6、AQS锁的类别?

AQS分为“排他锁”和“共享锁”两种:

  • 1.排他锁:是指在同一时刻只能有一个线程去占有锁,其他线程既不可以读,也不可以写,比如ReentrantLock;
  • 2.共享锁:是指在同一时刻可以有多个线程去占有锁,其他线程可以读,但不可以写,比如CountDownLatch计数器和Semaphore信号量;
    另外AQS也支持同时实现独占和共享两种方式,比如ReentrantReadWriteLock。

7、CountDownLatch和Semaphore的区别和底层原理?

底层都是通过AQS实现的。

  • CountDownLatch计数器:比如某个任务分为N个子线程并发去执行,state 也初始化为N;每个子线程执行完后调用countDown方法,state会减1,当所有子线程都执行完后,即state=0 ,然后会依次去唤醒AQS中排队的线程。
  • Semaphore信号量:我们常常用它来控制对有限资源的访问,每次使用资源前,先申请一个信号量,如果信号量不够,就会阻塞等待,并通过AQS来排队,当某个线程释放资源后,就释放一个信号量,然后会依次去唤醒AQS中排队的线程。

8、ReentrantLock底层原理?

底层是通过AQS实现的,state 初始化为 0,表示无锁状态;A 线程调用lock(加锁) 时,会将 state加 1 ,直到 A 线程调用 unlock(释放锁) 到 state=0为止,其它线程才有机会获取该锁。当然,释放锁之前,A 线程自己是可以重复获取该锁的,state 会累加,这就是可重入的概念。但要注意,加了多少次就锁要释放多少次,这样才能保证state恢复到0。

9、Sychronized和ReentrantLock的区别?

  • 1、Sychronized是⼀个关键字,ReentrantLock是⼀个类;
  • 2、Sychronized会自动的加锁和释放锁,ReentrantLock需要手动加锁和释放锁;
  • 3、Sychronized是非公平锁,ReentrantLock可以选择公平锁或非公平锁(默认);
  • 4、Sychronized是JVM层⾯的锁,ReentrantLock是API层⾯的锁。
  • 5、ReentrantLock有个tryLock()方法,尝试抢占锁,不会造成阻塞,加到锁返回true。
    理解第4点:Sychronized锁的是对象,锁信息保存在JVM对象头中,ReentrantLock是通过AQS中一个volatile修饰的state 来标识锁的状态。

10、Synchronized 偏向锁有个开关,如果默认开启有什么缺点?

JDK15默认关闭偏向锁优化,如果要开启可以使用XX:+UseBiasedLocking,但使用偏向锁相关的参数都会触发deprecate警告。
原因:在现在的jdk中,偏向锁加锁时 带来的性能提升 从整体上看并没有过多的收益,但撤销锁的成本过高,需要在一个安全点停止拥有锁的线程,使其变成无锁状态。

11、CAS与传统synchronized区别

CAS工作原理是基于乐观锁且操作是原子性的,与synchronized的悲观锁(底层需要调用操作系统的mutex锁)相比,效率也会相对高一些。

12、CAS是不是操作系统执行的?

不是,CAS是主要是通过处理器的指令来保证原子性的 。

13、说一下CAS怎么用的,会有哪些问题?

CAS就是我们所说的比较和交换,是采用的乐观锁技术,来实现线程安全的问题。
CAS有三个属性:旧值(A)、新值(B)、内存对象(V);
CAS原理就是对V进行赋值时,先判断原来的值是否为A,如果为A,就把新值B赋值到V,如果原来的值不是A(代表V的值放生了变化),那么会通过循环再走CAS流程,直到能够把新值B赋值成功(会给CPU带来很大的开销)。
CAS会有ABA的问题,可以通过加版本号,更新的时候时候不仅比较值,还比较版本号。

14、cas自增 和 Synchronized 自增 谁快?

  • 1、在线程数较少的时候,CAS实现比较快,性能优于synchronized,因为synchronized是悲观锁,存在锁竞争,会造成阻塞。
  • 2、当线程数大于一定数量的时候,CAS性能就不如synchronized了,因为多个线程在循环调用CAS接口,虽然不会让其他线程阻塞,但是这个时候竞争激烈,会导致CPU到达100%,同时会消耗更多时间。

15、Synchronized 和 ReentracLock 哪个快,为啥?

Synchronized 底层实现由JVM保证:在JVM运行过程中,可能出现偏向锁,轻量级锁,重量级锁。

  • 偏向锁:当线程第一次获取到锁的时候,将对象头中的 mark word 中的偏向锁线程的标识设为自己的id,当有其他线程竞争锁的时候,发现偏向锁线程的标识并不是自己,会进行一次 CAS替换,如果不成功,就会将锁升级为轻量级锁(消耗极少);
  • 轻量级锁:当前线程会将对象头中的 mark Word 复制到自己的栈空间中,然后通过自旋来获取锁,自旋10次还是获取锁失败,说明当前锁存在竞争,会将对象头的锁标识改为重量级指针,锁会膨胀为重量级锁(消耗:复制和自旋);
  • 重量级锁:需要操作系统实现线程之间的切换,这就需要从用户态转换到内核态,这个成本非常高,状态之间的转换需要相对比较长的时间。
  • ReentrantLock 底层是基于 AQS 实现:AQS 内部维护了一个volatile修饰的state标识以及⼀个双向链表队列(FIFO),volatile 消耗小于 Synchronized 。
    因此性能比较:偏向锁 > 轻量级锁 > ReentrantLock > Synchronized。

16、怎么检测一个线程是否拥有锁?

在 java.lang.Thread 中有一个 holdsLock()方法,拥有锁会返回 true 。

17、事务未提交而锁提前释放了?

高并发情况下,数据库事务未提交,但是锁已经释放。

  • 1、把整个事务单独封装成一个方法, 放在锁的工作范围之内;
  • 2、手动提交事务,因为事务是方法结束后才提交,我们可以手动提交事务;
  • 3、采用的Redisson可重入锁,提供watchdog机制,在锁释放前默认每10s重置锁失效时间为30s。

18、AtomicInteger计数器自增到一万以后,怎么归零?

volatile仅仅保证变量在线程间保持可见性,却依然不能保证非原子性的操作,还是用AtomicInteger类。
使用AtomicInteger.set(0)或.getAndSet(0)。

GIT

19、什么是git?

Git是一款开源的分布式版本控制系统。

20、列举工作中常用的git命令:

  • 将项目下载至本地:git clone http://xxx.git;
  • 提交文件:git commit -m “修复bug”;
  • 多次提交合成一次提交:git rebase -i HEAD~n;
  • 推送代码:git push origin dev;
  • 拉取并合并代码:git pull origin dev 等价于 git fetch origin dev + git merge origin/dev;
  • 查看历史记录:git log ;
  • 撤回(回退)版本:git revert(reset --hard) 提交id;
  • 创建并切换分支:git checkout -b dev 等价于git branch dev + git checkout dev;
  • 查看本地(远程/所有)分支:git branch(-r/-a);
  • 备份(恢复)当前工作区的内容:git stash(pop)

总结

都已经看到这里啦,赶紧收藏起来,祝您工作顺心,生活愉快!