前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《Java面试题集中营》- Java 锁

《Java面试题集中营》- Java 锁

作者头像
阿提说说
发布2024-07-14 08:33:51
590
发布2024-07-14 08:33:51
举报
文章被收录于专栏:Java技术进阶

《Java并发编程的艺术》、《Java并发编程之美》

乐观锁和悲观锁

两者原本是数据库中的概念,但Java锁中也有类似的思想。

乐观锁:认为数据在一般情况下不会造成冲突,在访问记录前不会加排他锁,而是在进行数据提交更新时,才会对数据冲突与否进行检测

悲观锁:认为数据很容易被其他线程修改,在处理数据前就加锁,并在整个数据处理过程中数据都处于锁定状态

公平锁、非公平锁

公平锁:根据线程请求锁的顺序来获取锁 非公平锁:抢占式获取锁

什么是死锁

具备以下4个条件就会产生死锁:

  • 互斥条件:指线程对已经获取到的资源进行排它性使用,即该资源同时只由一个线程占用。如果此时还有其他线程请求获取该资源,则请求者只能等待,直至占有资源的线程释放该资源
  • 请求并持有条件:指一个线程已经持有了至少一个资源,但又提出了新的资源请求,而新的资源已被其他线程占有,所以当前线程会被阻塞,但阻塞的同时并不释放自己已经获取的资源
  • 不可剥夺条件:指线程获取到的资源在自己使用完之前不能被其他线程抢占,只有在自己使用完毕后才由自己释放该资源
  • 循环等待:发生死锁的时候,必然存在一个(线程-资源)的环形链,即线程一在等待线程二占用的资源,线程二在等待线程三等待的资源…

例如:

代码语言:javascript
复制
public class DeadLock {

    private static Object obj = new Object();
    private static Object obj2 = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            System.out.println("线程1,开始获取obj1锁");
            synchronized (obj) {
                System.out.println("线程1,获取obj1锁成功");
                System.out.println("线程1,开始获取obj2锁");
                //休眠让两个线程都获取到对应的锁
                sleep(1);
                synchronized (obj2) {
                    System.out.println("线程1,获取obj2锁成功");

                }
            }
        });
        t.start();

        Thread t2 = new Thread(() -> {
            System.out.println("线程2,开始获取obj2锁");
            synchronized (obj2) {
                System.out.println("线程2,获取obj2锁成功");
                System.out.println("线程2,开始获取obj1锁");
                sleep(1);
                synchronized (obj) {
                    System.out.println("线程2,获取obj1锁成功");
                }
            }
        });
        t2.start();
        t2.join();
    }

    public static void sleep(long second){
        try {
            Thread.sleep(second * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
如何 避免死锁

使线程按照指定的顺序获取锁,并释放锁

代码语言:javascript
复制
Thread 1:
  lock A 
  lock B

Thread 2:
   wait for A
   lock C (when A locked)

Thread 3:
   wait for A
   wait for B
   wait for C

设置获取锁超时时间,超过时间,自动放弃获取锁,并释放已经持有的锁,使用lock.tryLock(timeount)代替synchronized

避免一个线程同时获取多个锁

什么是活锁

活锁也是一种死锁,死锁的话,所有线程都处于阻塞状态,活锁是由于某些条件没有满足,导致一直重复尝试,但有可能自行解开,比如设置了重试次数限制,或者超时时间

sleep 、wait、yield的区别

sleep:

  • 让当前线程休眠指定时间
  • 不释放锁资源
  • 可通过调用interrupt()方法来唤醒休眠线程

wait:

  • 让当前线程进入等待状态,当其他线程调用notify或者notifyAll方法时,当前线程进入就绪状态
  • 当前线程会释放已获取的锁资源,并进入等待队列
  • 只能在synchronized中使用

yield:

  • 让出线程当前的CPU执行时间,当前线程进入就绪状态,不阻塞当前线程,只是让同优先级或者更高优先级的线程优先执行
  • 线程下次调度时依旧有可能执行到
什么是虚假唤醒?如何避免

https://blog.csdn.net/LuckyBug007/article/details/70053669

wait(),notify()源码分析:https://www.jianshu.com/p/f4454164c017

Synchronized原理

Synchronized可以修饰普通方法、同步方法块、静态方法; 普通方法锁是当前实例对象

静态方法锁是当前类的Class对象

同步方法块锁是Synchonized配置的对象; 用的锁是存在对象头里的,根据mark word的锁状态来判断锁,如果锁只被同一个线程持有使用的是偏向锁,不同线程互相交替持有锁使用轻量级锁,多线程竞争使用重量级锁。锁会按偏向锁->轻量级锁->重量级锁 升级,称为锁膨胀 https://github.com/farmerjohngit/myblog/issues/12

synchronized和Lock的区别
  1. synchronized 是Java内置关键字,Lock是Java类
  2. synchronized 无法显式的判断是否获取锁的状态,Lock可以判断是否获取到锁
  3. synchronized 会自动释放锁,Lock需要在finally中手工释放锁
  4. synchronized 不同线程获取锁只有一个线程能获取成功,其他线程会一直阻塞直到获取锁,Lock有阻塞锁,也有非阻塞锁,阻塞锁还有尝试设置,功能更强
  5. synchronized 可重入,不可中断,非公平,Lock锁可重入,可判断,有公平锁,非公平锁
  6. Lock锁适合大量同步代码的同步问题,synchronized锁适合代码少量的同步问题
synchronized 可重入是怎么实现的

可重入是指:当一个线程持有一个锁对象之后,再次去获取同一个锁时能够成功获取。

因为synchronized使用的是锁对象,当某个线程第一次持有锁后,会修改锁对象的mark word锁状态为偏向锁,偏向锁会在当前线程的栈帧中建立一个锁记录空间,mark word会将指针指向栈中的锁记录。当线程再次获取锁对象的时候,会检查mark word 中的指针是否指向当前线程的栈帧,如果是就直接获取锁,如果不是就需要竞争

ReentrantLock可重入性怎么实现的?

由于ReentrantLock是通过AQS来实现的,其使用了AQS的state状态值来表示线程获取该锁的可重入次数,默认情况下state为0表示当前锁没有被任何线程持有,当一个线程获取该锁时会尝试使用CAS设置state值为1,如果CAS设置成功则当前线程获取了该锁,然后记录该锁的持有者为当前线程,在该线程没有释放锁的情况下第二次获取该锁后,状态值被设置2,这就是可以重入次数,在释放锁的时候,需要通过CAS将状态值减1,直到状态值为0,表示当前线程释放该锁

非公平锁和公平锁在ReetrantLock里的实现过程是怎样的

如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,FIFO。

对于非公平锁,只要CAS设置同步状态成功,则表示当前线程获取了锁,而公平锁还需要判断当前节点是否有前驱节点,如果有,则表示有线程比当前线程更早请求获取锁,因此需要等待前驱线程获取并释放锁之后才能继续获取锁。

自旋锁、自适应自旋、锁消除、锁粗化、轻量级锁、偏向锁、重量级锁概念

自旋锁:开启线程执行一个忙循环,直到需要更新的值为期待值为止 自适应自旋:自旋时间不再固定,由前一次在同一个锁上的自旋时间及锁的拥有者状态来决定,比如在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进而它将自旋等待更长时间,以期望成功获取锁,如果很少成功获得过锁,那很可能会忽略掉自旋过程,以避免CPU资源浪费。 锁消除:JIT在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除 锁粗化:如果虚拟机探测到有一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展到整个序列的外部 轻量级锁:加锁是通过同步对象的对象头进行操作的,首先会在当前线程的栈帧中建立一个名为锁记录的空间,存储锁对象目前的Mark Word拷贝,会加Displaced前缀,然后通过CAS尝试将对象的Mark Word更新为指向Lock Record的指针,如果成功,就获得了该对象的锁,如果失败,会检查Mark Word是否指向当前线程的栈帧,如果是就说明已经获得了锁,如果没有就说明有其他线程抢占,轻量锁就会膨胀成重量级锁;解锁也是通过CAS来操作,就是将Mark Word 替换为原来的值 偏向锁:锁偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要再同步。-XX:+UseBiasedLocking 开启偏向锁

重量级锁:也叫互斥锁,一种悲观锁,会阻塞线程,通过对象内部的monitor锁来实现,monitor锁依赖底层操作系统的MutexLock互斥锁来实现

AbstractQueuedSynchronizer的作用

抽象同步队列简称AQS,是实现同步器的基础组件,并发包中的锁都是基于其实现的,关键是先进先出的队列,state状态,并且定义了 ConditionObject ,拥有两种线程模式,独占模式和共享模式

  • AQS核心思想 如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制使用CLH队列实现的,即将暂时获取不到锁的线程加入到队列中 CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配, 并保持了上下节点,当前请求资源的线程
JDK8新增的锁

StampedLock 提供了三种模式的读写控制,当调用获取锁的系列函数时,会返回一个long型变量,支持在一定条件下三种模式的相互转换 写锁writeLock: 一个排它锁或者独占锁,并且写锁不可重入 悲观读锁readLock: 共享锁,在没有线程独占获取写锁的情况下,多个线程可以同时获取该锁,如果已经有其他线程持有写锁,则其他线程请求读锁会被阻塞 乐观读锁tryOptimisticRead: 在操作数据前并没有通过CAS设置锁的状态,仅通过位运算测试


坚持专研Java,一条路走到黑。持续更新地址 语雀:https://www.yuque.com/itsaysay/mzsmvg GitHub: https://github.com/jujunchen/Java-interview-question

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-07-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 乐观锁和悲观锁
  • 公平锁、非公平锁
  • 什么是死锁
  • 如何 避免死锁
  • 什么是活锁
  • sleep 、wait、yield的区别
  • 什么是虚假唤醒?如何避免
  • Synchronized原理
  • synchronized和Lock的区别
  • synchronized 可重入是怎么实现的
  • ReentrantLock可重入性怎么实现的?
  • 非公平锁和公平锁在ReetrantLock里的实现过程是怎样的
  • 自旋锁、自适应自旋、锁消除、锁粗化、轻量级锁、偏向锁、重量级锁概念
  • AbstractQueuedSynchronizer的作用
  • JDK8新增的锁
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档