JAVA面试常见问题之锁机制篇

1、说说线程安全问题,什么是线程安全,如何保证线程安全

  • 线程安全:就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
  • 如何保证
    • 使用线程安全的类;
    • 使用synchronized同步代码块,或者用Lock锁;
    • 多线程并发情况下,线程共享的变量改为方法局部级变量;

2、重入锁的概念,重入锁为什么可以防止死锁

概念自己可以获取自己的内部锁。当线程请求自己持有的对象锁时,如果锁是重入锁,线程请求成功。

防止死锁原因

3、产生死锁的四个条件

  • 互斥:某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
  • 占有且等待:一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
  • 不可抢占:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
  • 循环等待:存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。

参考:https://blog.csdn.net/guaiguaihenguai/article/details/80303835

4、如何检查死锁

  • 使用Jconsole,JDK自带的图形化界面。
  • 使用jstack输出线程dump信息到文件

以上两种方式请参考:http://www.cnblogs.com/flyingeagle/articles/6853167.html

5、volatile 实现原理

  作用:使用valatile修饰的成员变量,就是告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性。

  原理:

  • 强制把修改的数据写回内存。
  • 在多处理器情况下使多处理器缓存的数据失效

参考:https://mp.weixin.qq.com/s/XlowHAk8FPEYR3qtDIhJWw

6、synchronized 实现原理

作用:确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。

原理:被synchronized 修饰的代码区,当线程想进入的时候,须先获取对象监视器(相当于钥匙,只存在一把),获取对象监视器成功的进入被修饰代码区,没有获取对象监视器的被阻塞在同步块和同步方法的入口处,进入BLOCKED状态。

 

参考:https://mp.weixin.qq.com/s/XlowHAk8FPEYR3qtDIhJWw

7、synchronized 与 lock 的区别

      获取Lock对象的方法:Lock lock = new ReentrantLock();

  1. 首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
  2. synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
  3. synchronized会自动释放锁,Lock需在finally中手工释放锁(lock.unlock()方法释放锁),否则容易造成线程死锁;
  4. 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
  5. synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
  6. Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
  7. Lock锁可以设置等待时间,到了时间自动放弃获取锁

参考:https://www.cnblogs.com/iyyy/p/7993788.html

8、AQS同步队列

    队列同步器。它是构建锁或者其他同步组件的基础框架,通过这个框架可以简单实现一些相关锁。

参考:https://www.cnblogs.com/wait-pigblog/archive/2018/07/16/9315700.html

9、CAS无锁的概念、乐观锁和悲观锁

       概念:CAS(compare and swap)是一种比较交换技术,可以用来鉴别线程技术,一旦检测到冲突就充当当前操作指导直到解决冲突。CAS机制中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。

     乐观锁:大多基于版本来实现。将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为提交的数据是过期数据。

     悲观锁:对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
参考:https://blog.csdn.net/liubenlong007/article/details/53761730

10、常见的原子操作类

java.util.concurrent.atomic包,这个包中的原子操作类,提供了一种用法简单,性能高效,线程安全的更新一个变量的方式。

AtomicInteger、AtomicLong、AtomicBoolean、AtomicIntegerArray、AtomicReference(引用类型)、AtomicIntegerFieldUpdater(原子更新整形属性的更新器)

11、什么是ABA问题,出现ABA问题JDK是如何解决的

ABA问题:ABA问题出现在多线程或多进程计算环境中。

ABA问题举例描述:假设两个线程T1和T2访问同一个变量V,当T1访问变量V时,读取到V的值为A;此时线程T1被抢占了,T2开始执行,T2先将变量V的值从A变成了B,然后又将变量V从B变回了A;此时T1又抢占了主动权,继续执行,它发现变量V的值还是A,以为没有发生变化,所以就继续执行了。这个过程中,变量V从A变为B,再由B变为A就被形象地称为ABA问题了。

JDK解决方式:java提供了AtomicMarkableReference和AtomicStampedReference类帮助解决这个问题。AtomicStampedReference是利用版本戳的形式记录了每次改变以后的版本号

 12、乐观锁的业务场景及实现方式

业务场景:适合读取操作比较频繁的操作。

实现方式:使用版本标识的方式来确定读到的数据和提交的数据是否一致。提交后修改版本标识,当检测出不一致时就丢弃或重试。

13、Java 8并法包下常见的并发类

ConcurrentHashMap、CopyOnWriteArrayList

14、偏向锁、轻量级锁、重量级锁、自旋锁的概念

  • 偏向锁如果一个线程获得了锁,那么锁就进入了偏向模式。当这个线程再次请求锁时,无需再做任何同步操作。
  • 轻量级锁简单的将对象头部作为指针,指向持有锁的线程堆栈的内部,来判断一个线程是否持有对象锁。
  • 重量级锁
  • 自旋锁轻量级锁就会膨胀为重量级锁后,虚拟机为了避免线程真实的在操作系统层面挂起,虚拟机还会在做最后的努力
原文地址:https://www.cnblogs.com/lu51211314/p/10237154.html