乐趣区

关于java:Java面试问题汇总锁

1. 乐观锁和乐观锁

两者本来是数据库中的概念,但 Java 锁中也有相似的思维。

乐观锁:认为数据在个别状况下不会造成抵触,在拜访记录前不会加排他锁,而是在进行数据提交更新时,才会对数据抵触与否进行检测

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

2. 偏心锁、非偏心锁

偏心锁:依据 线程 申请锁的程序 来获取锁
非偏心锁:抢占式 获取锁

3. 什么是死锁

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

  • 互斥条件 :指线程对曾经获取到的资源进行 排它性应用 ,即该资源同时 只由一个线程占用*。如果此时还有其余线程申请获取该资源,则请求者只能期待,直至占有资源的线程开释该资源
  • 申请并持有条件 :指 一个线程曾经持有了至多一个资源 ,但又 提出了新的资源申请 ,而 新的资源已被 其余线程 占有 ,所以以后线程会被阻塞,但 阻塞的同时并不开释 本人曾经获取的资源
  • 不可剥夺条件 :指线程获取到的 资源在本人应用完之前不能被其余线程抢占,只有在本人应用结束后才由本人开释该资源
  • 循环期待 :产生死锁的时候,必然存在一个(线程 - 资源)的环形链,即 线程一在期待线程二占用的资源,线程二在期待线程三期待的资源

例如:

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();
        }
    }
}

4. 如何防止死锁

  1. 使线程 依照指定的程序获取锁,并开释锁

    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
  2. 设置获取锁超时工夫 ,超过工夫,主动放弃获取锁,并开释曾经持有的锁,应用 lock.tryLock(timeount) 代替 synchronized
  3. 防止一个线程同时获取多个锁

5. 什么是活锁

活锁也是一种死锁 ,死锁的话,所有线程都处于阻塞状态,活锁是 因为某些条件没有满足,导致始终反复尝试 ,但有 可能自行解开,比方设置了重试次数限度,或者超时工夫

6.sleep、wait、yield 的区别

sleep:

  • 让以后线程休眠指定工夫
  • 不开释锁资源
  • 可通过调用 interrupt()办法来唤醒休眠线程

wait:

  • 让以后线程进入期待状态,当其余线程调用 notify 或者 notifyAll 办法时,以后线程进入就绪状态
  • 以后线程会开释已获取的锁资源,并进入期待队列
  • 只能在 synchronized 中应用

yield:

  • 让出线程以后的 CPU 执行工夫,以后线程进入就绪状态, 不阻塞以后线程,只是让同优先级或者更高优先级的线程优先执行
  • 线程下次调度时仍旧有可能执行到

7. 什么是虚伪唤醒?如何防止

https://blog.csdn.net/LuckyBu…

wait(),notify()源码剖析:https://www.jianshu.com/p/f44…

8.Synchronized 原理

Synchronized 能够润饰一般办法、同步办法块、静态方法;

  • 一般办法锁是以后实例对象
  • 静态方法锁是以后类的 Class 对象
  • 同步办法块锁是 Synchonized 配置的对象;
    用的锁是存在对象头里的,依据 mark word 的锁状态来判断锁 ,如果锁只被同一个线程持有应用的是偏差锁,不同线程相互交替持有锁应用轻量级锁,多线程竞争应用重量级锁。 锁会按偏差锁 (单线程持有)-> 轻量级锁(两线程竞争)-> 重量级锁(多线程竞争) 降级,称为锁收缩
    https://github.com/farmerjohn…

9.synchronized 和 Lock 的区别

  1. synchronized 是 Java内置关键字,Lock 是Java 类
  2. synchronized 无奈 显式的判断是否获取锁的状态,Lock 能够判断是否获取到锁
  3. synchronized 会主动 开释锁,Lock 须要在 finally 中手工开释锁
  4. synchronized 不同线程获取锁只有一个线程能获取胜利,其余线程会始终 阻塞 直到获取锁,Lock 有阻塞锁,也有非阻塞锁,阻塞锁还有尝试设置,性能更强
  5. synchronized 可重入,不可中断,非偏心,Lock 锁可重入,可判断,有偏心锁,非偏心锁
  6. Lock 锁适宜大量同步代码的同步问题,synchronized 锁适宜代码大量的同步问题

10.synchronized 可重入是怎么实现的

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

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

11.ReentrantLock 可重入性怎么实现的?

因为 ReentrantLock 是通过 AQS 来实现的,其应用了 AQS 的 state 状态值来示意线程获取该锁的可重入次数,默认状况下 state 为 0 示意以后锁没有被任何线程持有,当一个线程获取该锁时会尝试应用 CAS 设置 state 值为 1,如果 CAS 设置胜利则以后线程获取了该锁,而后记录该锁的持有者为以后线程,在该线程没有开释锁的状况下第二次获取该锁后,状态值被设置 2,这就是能够重入次数,在开释锁的时候,须要通过 CAS 将状态值减 1,直到状态值为 0,示意以后线程开释该锁

12.AbstractQueuedSynchronizer 的作用

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

  • AQS 核心思想

    如果被申请的共享资源闲暇,则将以后申请资源的线程设置为无效的工作线程,并且将共享资源设置为锁定状态。如果被申请的共享资源被占用,那么就须要一套线程阻塞期待以及被唤醒时锁调配的机制,这个机制应用 CLH 队列实现的,行将临时获取不到锁的线程退出到队列中

    CLH(Craig,Landin,and Hagersten)队列是一个虚构的双向队列(虚构的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条申请共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的调配, 并放弃了高低节点,以后申请资源的线程

13. 非偏心锁和偏心锁在 ReetrantLock 里的实现过程是怎么的

如果一个锁是偏心的,那么锁的获取程序就应该合乎申请的相对工夫程序,FIFO。

对于非偏心锁,只有 CAS 设置同步状态胜利,则示意以后线程获取了锁,而偏心锁还须要判断以后节点是否有前驱节点,如果有,则示意有线程比以后线程更早申请获取锁,因而须要期待前驱线程获取并开释锁之后能力持续获取锁。

14. 自旋锁、自适应自旋、锁打消、锁粗化、轻量级锁、偏差锁、重量级锁概念

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

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

15.JDK8 新增的锁

StampedLock 提供了三种模式的读写管制,当调用获取锁的系列函数时,会返回一个 long 型变量,反对在肯定条件下三种模式的互相转换

写锁 writeLock: 一个排它锁或者独占锁,并且写锁不可重入

乐观读锁 readLock: 共享锁,在没有线程独占获取写锁的状况下,多个线程能够同时获
取该锁,如果曾经有其余线程持有写锁,则其余线程申请读锁会被阻塞

乐观读锁 tryOptimisticRead: 在操作数据前并没有通过 CAS 设置锁的状态,仅通过位运算测试

退出移动版