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)














