一、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的实现通常会提供更好的性能