简直了,被“Java并发锁”问题追问到自闭...
故事
地铁上,小帅双目空洞地望着窗外...绝望,发自内心地感到绝望...
距离失业已经过去两个月了,这是小帅接到的第四次面试邀请。“回去等通知吧...”,简简单单的六个字,把小帅的心再次打入了冰窖。
上次“【ThreadLocal问出花】”,小帅其实也有吸取教训得,这次对于多线程的问题还是做了很多准备的...可是没想到这次的结果居然也还是这样。
“Java中的锁了解吧?介绍一下吧”,面试官不紧不慢地问到。
“乐观锁、悲观锁、公平锁、非公平锁,然后平时咱们的synchronized是基于.....”小帅把知道的所有关于锁的基本都回答了一遍。
面试官对他笑了笑,“就这些吗?还有呢?比如自旋锁、可重入锁、独占锁....并且说一下你的理解,或者聊一下使用场景的优劣吧。”
“额.....以前好像看到过...”小帅语无伦次地回答到。
“嗯,行吧,之前的那些答得可以的,不过一会我这边有个会,要不今天咱们就聊到这里?回去等通知吧...”
Java中让人眼花缭乱的锁你是否真的一一清楚了?
试问这样一个大而宽的问题,大家能够总结全吗,如果让各位来回答,能否回答完全呢?
我们在实际的并发编程中,常常遇到多个线程访问一个共享变量的情况,当同时对共享变量进行读写操作的时候,就会产生数据不一致的情况。为了保证资源获取的有序性,我们就常常会用到并发锁。
那么接下来咱们就来聊聊这些Java并发锁的理解吧。我们将从以下这些方面来一起回顾一下Java中的并发锁。
乐观锁和悲观锁:线程是否锁住同步资源
大家其实对乐观锁和悲观锁听说的比较多一些,所以咱们就先来聊聊这两种类型的锁。这两种类型的锁,本质区分是要看线程是否锁住同步资源。
先来看一下悲观锁。悲观锁就是每次去拿数据的时候都会认为别人会修改数据,所以在读取数据的时候都会上锁。这样就会导致线程临时阻塞。
再来看一下乐观锁,乐观锁就是每次在拿数据的时候都假设别人不会修改数据,所以都不会进行上锁;只有在更新数据的时候才去判断之前有没有别的线程更新了这条数据。如果没有更新,那么当前线程会自己修改数据并且写入成功。如果数据已经被其他线程更新了,那么会报错或者自动重试,例如下图。
上述两种锁,并没有优劣之分。只是看相关的场景然后分别去使用。
乐观锁:适用于写少读多的场景。因为不用上锁,释放锁,省去了锁的开销,从而提升了吞吐量。
悲观锁:适用于写多读少的场景。因为线程竞争激烈,如果使用乐观锁会导致线程不断进行重试,反而降低吞吐量。
共享锁和独占锁:多个线程是否共享同一把锁
并发场景下,如果多个线程能够共享一把锁,那么就是所谓的共享锁,如果不能,那么则为独占锁(其他命名:排他锁或者独享锁)。
共享锁指锁可以被多个线程持有。如果一个线程对数据加上共享锁,那么其他线程只能对数据再加共享锁,不能加独占锁。另外的共享锁的线程只能读数据,不能修改数据。如下图。
独占锁是指锁一次只能被一个线程持有,如果一个线程对数据加上独占锁,那么其他的线程则不能对该数据再加任何类型的锁。如果一个线程获取独占锁,那么则该线程既可以读数据又可以修改数据。
对于独占锁来说,大家比较熟悉的就是synchronized和J.U.C包中的Lock实现类。
大家可能也听说过互斥锁,其实互斥锁就是独占锁的一种常规实现。
读写锁是共享锁的一种具体实现。读写锁管理一组锁,一个是只读的锁,一个是写锁。
读锁可以再没有写锁的时候被多个线程同时持有,而写锁是独占的,于此同时写锁的优先级要高于读锁,一个获得了读锁的线程必须能看到前一个释放的写锁更新的内容。
读写锁和互斥锁对比,其性能更高,每次只有一个写线程,但是有多个线程可以并发读。
例如,ReentrantReadWriteLock。具体伪代码如下:
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 公众号:程序员老猫
**/
public class ReadWriteLockDemo {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void readData() {
lock.readLock().lock(); // 获取读锁
try {
// 读取共享数据
} finally {
lock.readLock().unlock(); // 释放读锁
}
}
public void writeData() {
lock.writeLock().lock(); // 获取写锁
try {
// 修改或写入数据
} finally {
lock.writeLock().unlock(); // 释放写锁
}
}
}
公平锁和非公平锁:多线程竞争时是否要排队
我们根据多线程在竞争锁的时候是否需要排队从来判断其锁的类型是公平锁还是非公平锁。
公平锁指多个线程按照申请锁的顺序来获取锁。类似食堂排队打饭,先到的可以先打饭。
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序进行的,有可能后申请的比先申请的优先获得锁,高并发场景下,优先级就有可能发生反转。如下图:
咱们在日常开发的过程中经常用到synchronized,其底层其实就是非公平锁。当然如果我们要使用公平锁的情况下,我们也可以使用ReentrantLock。
伪代码如下:
Lock lock = new ReetrantLock(false);
ReentrantLock默认为非公平锁,设置为true的时候表示公平锁。当设置为false的时候表示非公平锁。
可重入锁和不可重入锁:同一个线程中多个流程是否能够获取同一把锁。
如果一个线程中的多个流程能够获取同一把锁,那么我们就叫该所为可重入锁,反之则为不可重入锁。咱们光看文字描述的话可能比较抽象。我们看一下下图。
在Java中可重入锁一般有ReentrantLock,其命名就已经很明确了。另外的synchronized也是可重入锁。
可重入锁的优势是可以一定程度上避免死锁发生。上面的示意图转换为如下demo:
public synchronized void methodA() {
methodB()
}
public synchronized void methodB() {
methodC()
}
public synchronized void methodC(){
doSomeThing()
}
自旋锁或者自适应自旋锁:线程锁定同步资源失败,如该线程没有被阻塞场景下发生
如果一个线程锁住同步资源失败,但是又希望这个线程不被阻塞,那么此时咱们就可以使用自旋锁或者自适应自旋锁。
自旋锁指线程没有获得锁的情况下不被挂起,而是执行一个忙循环。那么这个忙循环的话就成为自旋。如下:
目的:减少线程被挂起的概率,因为线程被挂起和唤醒也是消费资源。
Java中AtomicInteger类就有自旋的操作,如下源代码:
@HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}
上述方法中weakCompareAndSetInt(),就可以被称为是CAS操作,如果失败,那么会一直循环获取当前的value值然后进行重试操作。那么这个过程其实就是自旋了。
其他分类的锁。
上述我们聊到的这系列的锁应该是大家听到比较多的。其实还有其他的分类。在此不做一一展开了,有兴趣的小伙伴当然也可以深入去了解一下。
例如根据线程竞争同步资源的时候,细节流程是否发生变化,分为偏向锁、轻量级锁和重量级锁。
在比如,相信大家对HashMap底层原理倒背如流吧,对ConcurrentHashMap应该也有了解,那么ConcurrentHashMap底层其实将锁的粒度进一步细化了,存在了分段锁的概念等等。
总结
这些让人眼花缭乱的锁,如果面试官问到的话,大家是否能够说出一二呢?相信看完上面的解释,大家心里多多少少也有数了吧。当然关于最后一点其他分类的锁,老猫没有展开。有兴趣的小伙伴可以自行查阅一下这些分类。
简直了,被“Java并发锁”问题追问到自闭...的更多相关文章
- 深入理解 Java 并发锁
本文以及示例源码已归档在 javacore 一.并发锁简介 确保线程安全最常见的做法是利用锁机制(Lock.sychronized)来对共享数据做互斥同步,这样在同一个时刻,只有一个线程可以执行某个方 ...
- Java 并发锁
Java 中的锁 阻塞锁.可重入锁.读写锁.互斥锁.悲观锁.乐观锁.公平锁.偏向锁.对象锁.线程锁.锁粗化.锁消除.轻量级锁.重量级锁.信号量.独享锁.共享锁.分段锁 一.常见的锁 synchroni ...
- java并发锁ReentrantReadWriteLock读写锁源码分析
1.ReentrantReadWriterLock 基础 所谓读写锁,是对访问资源共享锁和排斥锁,一般的重入性语义为如果对资源加了写锁,其他线程无法再获得写锁与读锁,但是持有写锁的线程,可以对资源加读 ...
- 如何理解Java中眼花缭乱的各种并发锁?
在互联网公司面试中,很多小伙伴都被问到过关于锁的问题. 今天,我给大家一次性把Java并发锁的全家桶彻底讲明白.包括互斥锁.读写锁.重入锁.公平锁.悲观锁.自旋锁.偏向锁等等等等.视频有点长,大家一定 ...
- 百万并发中间件系统的内核设计看Java并发性能优化
“ 这篇文章,给大家聊聊一个百万级并发的中间件系统的内核代码里的锁性能优化. 很多同学都对Java并发编程很感兴趣,学习了很多相关的技术和知识.比如volatile.Atomic.synchroniz ...
- 深入并发锁,解析Synchronized锁升级
这篇文章分为六个部分,不同特性的锁分类,并发锁的不同设计,Synchronized中的锁升级,ReentrantLock和ReadWriteLock的应用,帮助你梳理 Java 并发锁及相关的操作. ...
- Java并发指南4:Java中的锁 Lock和synchronized
Java中的锁机制及Lock类 锁的释放-获取建立的happens before 关系 锁是java并发编程中最重要的同步机制.锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消 ...
- 【Java并发编程实战】----- AQS(二):获取锁、释放锁
上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放. AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断. acquireInte ...
- 【Java并发编程实战】-----“J.U.C”:CLH队列锁
在前面介绍的几篇博客中总是提到CLH队列,在AQS中CLH队列是维护一组线程的严格按照FIFO的队列.他能够确保无饥饿,严格的先来先服务的公平性.下图是CLH队列节点的示意图: 在CLH队列的节点QN ...
- 【Java并发系列04】线程锁synchronized和Lock和volatile和Condition
img { border: solid 1px } 一.前言 多线程怎么防止竞争资源,即防止对同一资源进行并发操作,那就是使用加锁机制.这是Java并发编程中必须要理解的一个知识点.其实使用起来还是比 ...
随机推荐
- win32 - 从dll中导出api并使用
从User32.dll中导出MessageBoxW 代码: #include <Windows.h> #include <iostream> #include <func ...
- 细聊ASP.NET Core WebAPI格式化程序
前言 我们在使用ASP.NET Core WebApi时它支持使用指定的输入和输出格式来交换数据.输入数据靠模型绑定的机制处理,输出数据则需要用格式化的方式进行处理.ASP.NET Core框架已经内 ...
- InSAR处理软件——Gamma 安装教程
Gamma是由瑞士 GAMMA Remote Sensing 公司开发SAR数据处理软件,支持SAR数据全流程处理,是最InSAR最常用的软件.下面介绍该软件的安装流程,安装环境为Ubuntu16.0 ...
- Kotlin 协程四 —— Flow 和 Channel 的应用
目录 一. Flow 与 Channel 的相互转换 1.1 Flow 转换为 Channel 1.1.1 ChannelFlow 1.1.2 produceIn -- 将 Flow 转换为单播式 C ...
- 【Azure ACR+App Service】ACR WebHook请求App Service时遇见 401 Unauthorized
问题描述 App Service 支持从ACR中直接拉取镜像,并且可以配置持续部署(Continuous Deployment), 它是通过在ACR中添加一个Webhook,然后发送POST请求到 & ...
- 【Azure Developer】Github Action使用Azure/login@v1插件登录遇见错误的替代方案
问题描述 在使用 Github Action - Azure/login@v1 的插件时候,登录中国区Azure遇见了问题. Login YAML 内容: - name: 'Login via A ...
- 【Azure 存储服务】Azure Storage Account Queue中因数据格式无法处理而在一个小时内不在可见的问题
问题描述 在从Storage Account 队列中获取数据(Queue),在门户中,明显看见有数据,但是通过消费端代码去获取的时候,就是无法获取到有效数据的情况.获取消息的代码如下: 问题解答 经过 ...
- 【Azure 应用服务】App Service / Function App 修改系统时区为中国时区的办法(Azure中所有服务的默认时间都为UTC时间,转换为北京时间需要+8小时)
问题描述 在Azure的 App Service / Function App 服务中,如果是在Windows系统中,可以通过添加Application Setting来转换为中国时间(WEBSITE ...
- C++ //list容器 构造函数 //list赋值和交换 //list容器大小操作 //list插入和删除,移除 //清空 //list数据存取back(); front() //list 反转和排序
1 //list容器 构造函数 //list赋值和交换 //list容器大小操作 2 //list插入和删除,移除 //清空 //list数据存取back(); front() 3 //list 反转 ...
- 2、dubbo原理
图例说明: 图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口. 图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖 ...