1. 什么是AQS?
1.1. 概述
全称是AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架,它是构建锁或者其他同步组件的基础框架
AQS与Synchronized的区别
synchronized | AQS |
|---|---|
关键字,c++语言实现 | java语言实现 |
| 悲观锁,自动释放锁 | 悲观锁,手动开启和关闭 |
| 锁竞争激烈都是重量级锁,性能差 | 锁竞争激烈的情况下,提供了多种解决方案 |
AQS常见的实现类
-
ReentrantLock阻塞式锁 -
Semaphore信号量 -
CountDownLatch倒计时锁
1.2. 工作机制
- 在
AQS中维护了一个使用了volatile修饰的state属性来表示资源的状态,0表示无锁,1表示有锁 - 提供了基于
FIFO的等待队列,类似于Monitor的EntryList - 条件变量来实现等待、唤醒机制,支持多个条件变量,类似于
Monitor的WaitSet

- 线程0来了以后,去尝试修改
state属性,如果发现state属性是0,就修改state状态为1,表示线程0抢锁成功- 线程1和线程2也会先尝试修改
state属性,发现state的值已经是1了,有其他线程持有锁,它们都会到FIFO队列中进行等待,FIFO是一个双向队列,head属性表示头结点,tail表示尾结点
如果多个线程共同去抢这个资源是如何保证原子性的呢?

在去修改state状态的时候,使用的cas自旋锁来保证原子性,确保只能有一个线程修改成功,修改失败的线程将会进入FIFO队列中等待
AQS是公平锁吗,还是非公平锁?
-
新的线程与队列中的线程共同来抢资源,是非公平锁
-
新的线程到队列中等待,只让队列中的
head线程获取锁,是公平锁
比较典型的
AQS实现类ReentrantLock,它默认就是非公平锁,新的线程与队列中的线程共同来抢资源
2. ReentrantLock的实现原理
2.1. 概述
ReentrantLock翻译过来是可重入锁,相对于synchronized它具备以下特点:
-
可中断
-
可以设置超时时间
-
可以设置公平锁
-
支持多个条件变量
-
与
synchronized一样,都支持重入

2.2. 实现原理
ReentrantLock主要利用CAS+AQS队列来实现。它支持公平锁和非公平锁,两者的实现类似
构造方法接受一个可选的公平参数(默认非公平锁),当设置为true时,表示公平锁,否则为非公平锁。公平锁的效率往往没有非公平锁的效率高,在许多线程访问的情况下,公平锁表现出较低的吞吐量。
查看ReentrantLock源码中的构造方法:

提供了两个构造方法,不带参数的默认为非公平
如果使用带参数的构造函数,并且传的值为true,则是公平锁
其中NonfairSync和FairSync这两个类父类都是Sync

而Sync的父类是AQS,所以可以得出ReentrantLock底层主要实现就是基于AQS来实现的

工作流程

-
线程来抢锁后使用
cas的方式修改state状态,修改状态成功为1,则让exclusiveOwnerThread属性指向当前线程,获取锁成功 -
假如修改状态失败,则会进入双向队列中等待,
head指向双向队列头部,tail指向双向队列尾部 -
当
exclusiveOwnerThread为null的时候,则会唤醒在双向队列中等待的线程 -
公平锁则体现在按照先后顺序获取锁,非公平体现在不在排队的线程也可以抢锁
3. synchronized和Lock有什么区别 ?
参考回答
- 语法层面
synchronized是关键字,源码在jvm中,用c++语言实现Lock是接口,源码由jdk提供,用java语言实现- 使用
synchronized时,退出同步代码块锁会自动释放,而使用Lock时,需要手动调用unlock方法释放锁
- 功能层面
- 二者均属于悲观锁、都具备基本的互斥、同步、锁重入功能
Lock提供了许多synchronized不具备的功能,例如获取等待状态、公平锁、可打断、可超时、多条件变量Lock有适合不同场景的实现,如ReentrantLock,ReentrantReadWriteLock
- 性能层面
- 在没有竞争时,
synchronized做了很多优化,如偏向锁、轻量级锁,性能不赖 - 在竞争激烈时,
Lock的实现通常会提供更好的性能
- 在没有竞争时,
4. 死锁产生的条件是什么?
死锁:一个线程需要同时获取多把锁,这时就容易发生死锁
例如:
t1线程获得A对象锁,接下来想获取B对象的锁
t2线程获得B对象锁,接下来想获取A对象的锁
代码如下:
package com.dcxuexi.basic;
import static java.lang.Thread.sleep;
public class Deadlock {
public static void main(String[] args) {
Object A = new Object();
Object B = new Object();
Thread t1 = new Thread(() -> {
synchronized (A) {
System.out.println("lock A");
try {
sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (B) {
System.out.println("lock B");
System.out.println("操作...");
}
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (B) {
System.out.println("lock B");
try {
sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (A) {
System.out.println("lock A");
System.out.println("操作...");
}
}
}, "t2");
t1.start();
t2.start();
}
}
控制台输出结果

此时程序并没有结束,这种现象就是死锁现象…线程t1持有A的锁等待获取B锁,线程t2持有B的锁等待获取A的锁。
5. 如何进行死锁诊断?
当程序出现了死锁现象,我们可以使用jdk自带的工具:jps和jstack
步骤如下:
第一:查看运行的线程

第二:使用jstack查看线程运行的情况,下图是截图的关键信息
运行命令:jstack -l 46032

其他解决工具,可视化工具
jconsole
用于对jvm的内存,线程,类 的监控,是一个基于jmx的GUI性能监控工具
打开方式:java安装目录bin目录下 直接启动jconsole.exe就行
VisualVM:故障处理工具
能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,反向查看分配的堆栈
打开方式:java安装目录bin目录下 直接启动jvisualvm.exe就行
6. ConcurrentHashMap
ConcurrentHashMap是一种线程安全的高效Map集合
底层数据结构:
-
JDK1.7底层采用分段的数组+链表实现 -
JDK1.8采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。
6.1. JDK1.7中concurrentHashMap
数据结构

- 提供了一个
segment数组,在初始化ConcurrentHashMap的时候可以指定数组的长度,默认是16,一旦初始化之后中间不可扩容- 在每个
segment中都可以挂一个HashEntry数组,数组里面可以存储具体的元素,HashEntry数组是可以扩容的- 在
HashEntry存储的数组中存储的元素,如果发生冲突,则可以挂单向链表
存储流程

- 先去计算
key的hash值,然后确定segment数组下标 - 再通过
hash值确定hashEntry数组中的下标存储数据 - 在进行操作数据的之前,会先判断当前
segment对应下标位置是否有线程进行操作,为了线程安全使用的是ReentrantLock进行加锁,如果获取锁是被会使用cas自旋锁进行尝试
6.2. JDK1.8中concurrentHashMap
在JDK1.8中,放弃了Segment臃肿的设计,数据结构跟HashMap的数据结构是一样的:数组+红黑树+链表
采用CAS + Synchronized来保证并发安全进行实现
-
CAS控制数组节点的添加 -
synchronized只锁定当前链表或红黑二叉树的首节点,只要hash不冲突,就不会产生并发的问题 , 效率得到提升

7. 导致并发程序出现问题的根本原因是什么?
Java并发编程三大特性
-
原子性
-
可见性
-
有序性
(1)原子性
一个线程在CPU中操作不可暂停,也不可中断,要不执行完成,要不不执行
比如,如下代码能保证原子性吗?

以上代码会出现超卖或者是一张票卖给同一个人,执行并不是原子性的
解决方案:
1.synchronized:同步加锁
2.JUC里面的lock:加锁

(2)内存可见性
内存可见性:让一个线程对共享变量的修改对另一个线程可见
比如,以下代码不能保证内存可见性

解决方案:
-
synchronized -
volatile(推荐) -
LOCK
(3)有序性
指令重排:处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的
还是之前的例子,如下代码:

解决方案:
- volatile




![微信小程序:实名认证登录 [2018年]](https://img-blog.csdnimg.cn/6393da47fc4b45d9bd447f59cd8a48ab.png)














