【Java面试丨并发编程】线程中并发安全

一、Synchronized关键字的底层原理

1. Synchronized的作用

  • Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其他线程再想获取这个【对象锁】时就会阻塞住

2. Monitor

  • Synchronized【对象锁】底层是由Monitor实现,线程获得锁时需要使用对象(锁)关联的Monitor
    在这里插入图片描述
  • Monitor被翻译为监视器,由JVM提供,C++语言实现,在Monitor内部有三个属性,分别为Owner、EntryList、WaitSet
    在这里插入图片描述
    (1)Owner:存储当前获取锁的线程,有且只能存储一个线程
    (2)EntryList:存储没有抢到锁的线程,此处均为处于Blocked阻塞状态的线程
    (3)WaitSet:存储调用wait()方法的线程,此处均为Waiting等待状态的线程

3. 锁升级

  • Java中的Synchronized有偏向锁、轻量级锁、重量级锁三种形式,分别对应了锁只被一个线程持有、不同线程交替持有锁、多线程竞争锁三种情况
  • 在JDK 1.6引入了两种新型锁机制:偏向锁和轻量级锁,它们的引入是为了解决在没有多线程竞争或基本没有竞争的场景下,因使用传统锁机制带来的性能开销问题
    在这里插入图片描述
  • 总结:一旦锁发生了竞争,都会升级为重量级锁

二、谈谈JMM(Java内存模型)

  • Java内存模型(Java Memory Model,JMM):定义了共享内存中多线程程序读写操作的行为规范,通过这些规则来规范对内存的读写操作,从而保证指令的正确性
    在这里插入图片描述
  • 总结
    (1)JMM把内存分为两块,一块是私有线程的工作区域(工作内存),另一块是所有线程的共享区域(主内存)
    (2)线程跟线程之间是相互隔离的,线程跟线程之间交互需要通过主内存

三、CAS你知道吗

1. 介绍

  • CAS(Compare And Swap,比较再交换):体现了一种乐观锁的思想,在无锁的情况下,能够保证线程操作共享数据的原子性
  • 在JUC(java.util.concurrent)包下实现的很多类都用到了CAS操作
    (1)AbstractQueuedSynchronizer(AQS框架)
    (2)AtomicXXX类(原子类)
  • 在操作共享变量的时候使用的是自旋锁,效率上更高一些
  • CAS的底层是调用Unsafe类中的方法,都是操作系统提供的,其他语言实现

2. CAS数据交换流程

在这里插入图片描述

3. 自旋锁

  • 第一次CAS失败后,通过自旋操作,会重新加载主内存中共享变量V值到线程的工作内存中,将A值覆盖为新的V值,然后再去CAS操作,直到成功为止
  • 自旋锁:因为没有加锁,所以线程不会陷入阻塞状态,效率较高;但是如果线程之间竞争激烈,重试频繁发生,效率会受影响
    在这里插入图片描述

4. CAS底层实现

  • CAS底层依赖于一个Unsafe类来直接调用操作系统底层的CAS指令
    在这里插入图片描述
  • ReentrantLock中实现CAS的代码
    在这里插入图片描述

5. 乐观锁和悲观锁

  • 乐观锁
    (1)CAS基于乐观锁的思想,最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃点亏再重试
  • 悲观锁
    (1)Synchronized基于悲观锁的思想,最悲观的估计,得防着其他线程来修改共享变量,我上了锁,你们都别想改,我改完了解开锁,你们才有机会修改

四、什么是AQS

1. 介绍

  • AQS(AbstractQueuedSynchronizer,抽象队列同步器):是构建锁或者其他同步组件的基础框架
  • AQS和Synchronized的区别
    在这里插入图片描述
  • AQS常见的实现类
    (1)ReentrantLock:阻塞式锁
    (2)Semaphore:信号量
    (3)CountDownLatch:倒计时锁

2. AQS工作机制

  • AQS内部维护了一个先进先出的双向队列,队列中存储着排队的线程
  • AQS内部还有一个属性state,这个state就相当于一个资源,默认是0(无锁状态)。如果队列中有一个线程成功修改state为1,则当前线程就相当于获取了资源(锁)
  • 基本工作机制
    在这里插入图片描述
  • 多个线程共同去抢这个state资源,如何保证原子性
    在这里插入图片描述
  • AQS是公平锁,还是非公平锁
    在这里插入图片描述

五、ReentrantLock的实现原理

1. 特点

  • ReentrantLock为可重入锁,相对于Synchronized锁具备以下特点
    (1)可中断
    (2)可以设置超时时间
    (3)可以设置公平锁
    (4)支持多个变量
    (5)与Synchronized锁一样,都支持重入,调用lock()方法获取到锁之后,再次调用lock()方法是不会阻塞的

2. 实现原理

  • ReentrantLock主要利用CAS+AQS队列来实现的,支持公平锁和非公平锁

  • ReentrantLock的构造方法接收一个可选的公平参数(默认:非公平锁),当设置为true时,表示公平锁,否则为非公平锁
    在这里插入图片描述

  • 公平锁的效率往往没有非公平锁效率高,在多线程访问的情况下,公平锁表现出较低的吞吐量

  • 具体描述
    在这里插入图片描述

六、死锁产生的条件是什么

1. 介绍

  • 死锁:一个线程需要同时获取多把锁,这时候就容易发生死锁
    在这里插入图片描述

2. 如何进行死锁诊断

  • 当程序出现了死锁现象,可以使用JDK自带的工具:jps和jstack
    (1)jps:输出JVM中运行的进程状态信息
    (2)jstack:查看Java进程内线程的堆栈信息
    在这里插入图片描述
  • 其他工具查看
    (1)jconsole.exe
    在这里插入图片描述
    (2)VisualVM
    在这里插入图片描述

七、谈谈你对volatile的理解

1. 介绍

  • 一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义
    (1)保证线程之间的可见性
    (2)禁止进行指令重排

2. 保证线程之间的可见性

  • 用volatile修饰共享变量,能够防止编译器等优化发生,让一个线程对共享变量的修改对另一个线程可见
  • 可见性问题分析:主要是因为在JVM虚拟机中有一个JIT(即时编译器)给代码做了优化
    在这里插入图片描述
    (1)解决方案一:在程序运行的时候加入VM参数-Xint,表示禁用JIT即时编译器【不推荐,得不偿失,其他程序还要使用JIT】
    (2)解决方案二:在修饰共享变量的时候加上volatile,告诉JIT不要对volatile修饰的变量做优化

3. 禁止指令重排序

  • 用volatile修饰共享变量时,会在读、写共享变量时加入不同的屏障,阻止其他读写操作越过屏障,从而达到阻止重排序的效果
  • 指令重排序演示
    在这里插入图片描述
  • 解决指令重排序演示
    在这里插入图片描述
  • volatile使用技巧
    (1)写变量:让volatile修饰的变量在代码最后位置
    (2)读变量:让volatile修饰的变量在代码最开始位置

八、聊一下ConcurrentHashMap

1. 介绍

  • ConcurrentHashMap是一种线程安全的高效Map集合
  • 底层数据结构
    (1)JDK 1.7:采用分段的数组+链表实现
    (2)JDK 1.8:采用的数据结构和HashMap一样,数组+链表/红黑二叉树

2. 添加数据逻辑

  • JDK 1.7:ReentrantLock锁+CAS
    (1)整体流程
    在这里插入图片描述
    (2)添加数据
    在这里插入图片描述
  • JDK 1.8:Synchronized锁+CAS
    (1)CAS控制数组节点的添加
    (2)Synchronized只是锁定当前链表/红黑树二叉树的首节点,只要hash不冲突,就不会产生并发问题,效率得到提升
    (3)添加数据
    在这里插入图片描述

九、导致并发程序出现问题的根本原因是什么

1. 原子性

  • 一个线程在CPU中操作不可暂停,也不可中断,要不执行完成,要不不执行

2. 可见性

  • 一个线程对共享变量的修改对另一个线程可见

3. 有序性

  • 指令重排序:处理器为了提高程序运行效率,可能会对输入代码进行优化。它不保证程序中各个语句的执行先后顺序与代码中的顺序一致

4. 解决方案

  • 原子性:Synchronized、Lock
  • 可见性:volatile、Synchronized、Lock
  • 有序性:volatile

十、Synchronized锁和Lock锁有什么区别

1. 语法层面

  • Synchronized是Java关键字,源码在JVM中,由C++语言实现
  • Lock是接口,源码由JDK提供,Java语言实现
  • 使用Synchronized时,退出同步代码块,锁会自动释放
  • 使用Lock时,需要手动调用unlock()方法释放锁

2. 功能层面

  • 二者均属于悲观锁,都具备基本的互斥、同步、锁重入功能
  • Lock提供了许多Synchronized不具备的功能,比如:公平锁、可打断(lock.lockInterruptibly()方法&t1.interrupt()方法)、可超时(lock.tryLock(long time)方法)、多条件变量(lock.newCondition()方法&c1.await()方法&c1.signal()方法)
  • Lock有适合不同场景的实现,比如:ReentrantLock、ReentrantReadWriteLock(读写锁)

3. 性能层面

  • 在没有竞争时候,Synchronized做了很多优化,性能不赖,比如:偏向锁、轻量级锁
  • 在竞争激烈时候,Lock的实现通常会提供更好的性能
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值