一图学完!Java锁机制含面试题


写在前面:
本文是个人整理的锁相关知识点,分享出来 希望大家提出意见,少走弯路!
最后附上画的思维导图!(大部分概念经过个人精简方便记忆,有补充意见评论提出)

基本概念

Java锁是什么

锁(lock)或互斥(mutex)是一种同步机制,用于多线程环境下强制对资源的访问权限,旨在强制实施互斥排他,并发的控制策略

锁开销(lock overhead)

锁占用内存空间,cpu初始化和销毁锁,获取和释放锁的时间。程序用的锁越多,相应的开销越大

锁竞争(lock contention)

一个进程或线程试图获取另一个进程或线程持有的锁,就会发生竞争。锁粒度越小,发生锁竞争的可能性就越小

死锁(deadlock)

死锁的概念

至少两个任务中的每一个都等待另一个任务持有的锁的情况

死锁发生的条件

1.互斥条件: 一个资源每次只能被一个进程使用,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待
2.请求与保持条件: 进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放
3.不可剥夺条件: 进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)
4.循环等待条件: 即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路

锁的种类

1.公平锁/非公平锁(按申请顺序分类)

公平锁: 线程获取锁的顺序和调用lock的顺序一样,FIFO(较少用到)
公平锁实例:ReentrantLock(true)可实现公平锁

非公平锁: 线程获取锁的顺序和调用lock的顺序无关,全凭运气(大多锁都为非公平锁,提高效率)
非公平锁实例: ReenTrantLock默认是非公平锁。Synchronized只能是非公平锁
优点:吞吐量大
缺点:可能造成优先级反转或者饥饿现象

2.共享锁/独享锁(按资源是否只能被一个资源持有分类)

独享锁: 该锁一次只能被一个线程所持有
实例:Synchronized、ReentranLock、ReenWriteLock的写锁

共享锁: 该锁可以被多个线程所持有
实例:ReenWriteLock的读锁

3.互斥锁/读写锁(就是 共享锁/独享锁 的具体实现)

互斥锁: ReentranLock,Synchronized

读写锁: ReentrantReadWriteLock(其读锁是共享锁,其写锁是独享锁)

4.乐观锁/悲观锁(按并发情况下资源分配策略分类)

乐观锁: 认为一个数据的并发操作,是不会发生修改的,在更新数据的时候,会采用尝试更新而不直接加锁。(即不加锁策略,使用CAS判断,下面算法会提到)

实例:CAS(Compare and Swap)算法

使用场景:简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少)不加锁会带来大量的性能提升

悲观锁: 认为对同一数据的并发操作一定会发生修改,所以采取加锁方式。在一个资源被分配到线程期间,其他线程只能等待,造成效率较低,但不会有线程安全问题

实例:Synchronize关键字,Lock的三个实现类(ReentranLock以及ReentrantReadWriteLock类中的两个静态内部类ReadLock和WriteLock)

使用场景:悲观锁适合写操作非常多的场景

4.可重入锁(又名递归锁)

可重入锁: 可重入锁又名递归锁,指的是线程在获得锁之后,再次获取该锁不需要阻塞,而是直接关联一次计数器增加重入次数

实例:ReentrantLock,Synchronized

好处:可一定程度避免死锁

5.分段锁(对于ConcurrentHashMap的一种设计)

实例:ConcurrentHashMap

实现:在ConcurrentHashMap中当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入

6.偏向锁/轻量级锁/重量级锁(针对Synchronized,锁的粒度逐渐加大)

偏向锁: 指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。

轻量级锁: 指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。

重量级锁: 指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

7.自旋锁

自旋锁: 指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

相关算法

CAS(Compare and Swap)算法(适用于多读场景,冲突一般较少)

CAS实现: 包含三个参数CAS(V, E, N),V表示当前当前实际值,E表示预期值,N表示新值。仅当V等于E的时候,才会把V的值设置成N,否则不会执行任何操作(比较和替换是一个原子操作)。如果V值和E值不相等,则说明有其他线程修改过V值,当前线程什么都不做,最后返回当前V的真实值。CAS操作是抱着乐观的态度进行的,它总是认为自己可以成功的完成操作。当多个线程同时使用CAS操作一个变量时,只有一个会成功更新,其余都会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作

相关问题:
问题1: ABA问题:如果一个值原来是A,被其他线程变成了B,最后又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了
解决方法: 使用版本号,在变量前面追加上版本号,每次变量更新的时候把版本号+1,那么A-B-A 就会变成1A-2B-3A(Java中AtomicStampedReference内部使用Pair来存储元素值及其版本号,主要用来解决ABA问题。)

问题2: CAS导致自旋消耗:多个线程争夺同一个资源时,如果自旋一直不成功,将会一直占用CPU。
解决方法: 破坏掉for死循环,当超过一定时间或者一定次数时,return退出

问题3: CAS只能单变量:CAS的原子操作只能针对一个共享变量
解决方法: 对多个变量操作,1. 可以加锁来解决。2 .封装成对象类解决。

AQS(AbstractQueuedSynchronizer抽象队列同步器)

实现: AQS的实现依赖内部的FIFO双向队列,如果当前线程竞争锁失败,那么AQS会把当前线程以及等待状态信息构造成一个Node加入到同步队列中,同时再阻塞该线程。当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(线程)。
操作方式:
锁竞争添加节点:
1.新的线程封装成Node节点追加到同步队列中,设置prev节点以及修改当前节点的前置节点的next节点指向自己
2.通过CAS将tail重新指向新的尾部节点
锁释放移除节点:
1.修改head节点指向下一个获得锁的节点
2.新的获得锁的节点,将prev的指针指向null
资源共享方式: 独占与共享
AQS更多内容可能放到并发章节详细展开

面试题

Synchronized横切面详解

java源码层级: synchronized(对象)
字节码层级: monitorenter,moniterexit(加锁,解锁,C++通过指针实现)
JVM层级:(Hotspot的实现):在32位机系统里面,锁用三个bit做标记,其中一个bit表示是否为偏向锁,两个bit表示锁类型。
2个bit的标志位:00(轻量级锁定),01(未锁定),10(重量级锁),11(GC标记)
1个bit的偏向锁标志位:1为可偏向

Synchronized升级过程: 首先线程自动获取锁,此时为偏向锁(无线线程冲突情况)。
若此时被另一个线程所访问,则此锁就会升级为轻量级锁。其他线程会通过自旋尝试获取锁,不会阻塞,提高性能(有线程冲突情况)
若此时等待线程自旋(CAS)超过十次,则此轻量级锁会升级为重量级锁,重量级锁会让其他请求的线程进入阻塞,性能降低(线程冲突严重情况)

面试题1: 偏向锁一定能提高效率吗?
可借鉴回答:分情况,当明确的知道有多个线程竞争的时候,锁升级的过程也会消耗资源,所以一开始就应该用重量级锁

面试题2: 轻量级锁一定比重量级锁效率高吗?
可借鉴回答:未必,当线程自旋的时候会消耗cpu资源,而重量级锁会把其它线程阻塞,不消耗cpu资源

Synchronized和Lock的区别

1.Lock是一个接口(jdk实现),而synchronized是Java中的关键字,synchronized是内置的语言实现(jvm实现)

2.synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁

3.Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断

4.synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁

5.通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。

Synchronized和Lock的性能比较

1.在高争用,高耗时的环境下Synchronized效率更高

2.在低争用,低耗时的环境下,CAS效率更高

3.Synchronized到重量级之后是等待队列,不消耗CPU

4.CAS等待时间消耗CPU

关于两者的性能比较:一切以实测为准!

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq374461948/article/details/105751045