故事

地铁上,小帅双目空洞地望着窗外...绝望,发自内心地感到绝望...

距离失业已经过去两个月了,这是小帅接到的第四次面试邀请。“回去等通知吧...”,简简单单的六个字,把小帅的心再次打入了冰窖。

上次“【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并发锁”问题追问到自闭...的更多相关文章

  1. 深入理解 Java 并发锁

    本文以及示例源码已归档在 javacore 一.并发锁简介 确保线程安全最常见的做法是利用锁机制(Lock.sychronized)来对共享数据做互斥同步,这样在同一个时刻,只有一个线程可以执行某个方 ...

  2. Java 并发锁

    Java 中的锁 阻塞锁.可重入锁.读写锁.互斥锁.悲观锁.乐观锁.公平锁.偏向锁.对象锁.线程锁.锁粗化.锁消除.轻量级锁.重量级锁.信号量.独享锁.共享锁.分段锁 一.常见的锁 synchroni ...

  3. java并发锁ReentrantReadWriteLock读写锁源码分析

    1.ReentrantReadWriterLock 基础 所谓读写锁,是对访问资源共享锁和排斥锁,一般的重入性语义为如果对资源加了写锁,其他线程无法再获得写锁与读锁,但是持有写锁的线程,可以对资源加读 ...

  4. 如何理解Java中眼花缭乱的各种并发锁?

    在互联网公司面试中,很多小伙伴都被问到过关于锁的问题. 今天,我给大家一次性把Java并发锁的全家桶彻底讲明白.包括互斥锁.读写锁.重入锁.公平锁.悲观锁.自旋锁.偏向锁等等等等.视频有点长,大家一定 ...

  5. 百万并发中间件系统的内核设计看Java并发性能优化

    “ 这篇文章,给大家聊聊一个百万级并发的中间件系统的内核代码里的锁性能优化. 很多同学都对Java并发编程很感兴趣,学习了很多相关的技术和知识.比如volatile.Atomic.synchroniz ...

  6. 深入并发锁,解析Synchronized锁升级

    这篇文章分为六个部分,不同特性的锁分类,并发锁的不同设计,Synchronized中的锁升级,ReentrantLock和ReadWriteLock的应用,帮助你梳理 Java 并发锁及相关的操作. ...

  7. Java并发指南4:Java中的锁 Lock和synchronized

    Java中的锁机制及Lock类 锁的释放-获取建立的happens before 关系 锁是java并发编程中最重要的同步机制.锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消 ...

  8. 【Java并发编程实战】----- AQS(二):获取锁、释放锁

    上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放. AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断. acquireInte ...

  9. 【Java并发编程实战】-----“J.U.C”:CLH队列锁

    在前面介绍的几篇博客中总是提到CLH队列,在AQS中CLH队列是维护一组线程的严格按照FIFO的队列.他能够确保无饥饿,严格的先来先服务的公平性.下图是CLH队列节点的示意图: 在CLH队列的节点QN ...

  10. 【Java并发系列04】线程锁synchronized和Lock和volatile和Condition

    img { border: solid 1px } 一.前言 多线程怎么防止竞争资源,即防止对同一资源进行并发操作,那就是使用加锁机制.这是Java并发编程中必须要理解的一个知识点.其实使用起来还是比 ...

随机推荐

  1. 2021-07-20 value!==value,JavaScript中NaN

    关于NaN NaN 属性代表一个"不是数字"的number类型的字面量值.这个特殊的值是因为运算不能执行而导致的,不能执行的原因要么是因为其中的运算对象之一非数字. NaN的出现场 ...

  2. Vue框架设计:性能权衡的艺术

    "框架设计里到处都体现了权衡的艺术." 当我们设计一个框架的时候,框架本身的各个模块之间并不是相互独立的,而是相互关联.相互制约的.因此作为框架设计者,一定要对框架的定位和方向拥有 ...

  3. 详解SSL证书系列(3)如何选择SSL证书

    我们知道了在网站部署 SSL 证书后,不管是对网站本身还是对网站的用户都能够带来许多好处.那么随着 HTTPS的普及,市面上也出现了各种不同的 SSL 证书.并且由于 SSL 证书的多样性,很多人对于 ...

  4. 【Azure 应用服务】PHP项目部署到App Service for Linux环境中,如何修改上传文件大小的限制呢?

    问题描述 PHP项目部署到App Service for Linux环境中,如何修改上传文件大小的限制呢? 问题解答 经过查询Azure App Service官方文档,可能通过在项目根目录下添加.h ...

  5. gopkg.in/go-playground/validator中比较有用的标签

    -  忽略|  或omitempty 有则验证,空值则不验证dive  潜入到切片.数组.映射中,例如 NumList []int `validate:"len=2,dive,gt=18&q ...

  6. 图数据库|Nebula Graph v3.1.0 性能报告

    本文首发于 Nebula Graph Community 公众号 本文系 Nebula Graph 发行版 v3.1.0 的性能测试报告. 本文目录 测试环境 测试数据 关于 LDBC-SNB 测试说 ...

  7. welearn平台答案 大学英语 视听说 综合教程

    打开页面 1. F12 2. ctrl+shift+c 3. 鼠标移动到选项位置 4. 带data-solution的就是答案

  8. 掌握pandas cut函数,一键实现数据分类

    pandas中的cut函数可将一维数据按照给定的区间进行分组,并为每个值分配对应的标签.其主要功能是将连续的数值数据转化为离散的分组数据,方便进行分析和统计. 1. 数据准备 下面的示例中使用的数据采 ...

  9. rst文件查看(Sphinx)

    reStructuredText ( RST . ReST 或 reST )是一种用于文本数据的文件格式,主要用于 Python 编程语言社区的技术文档. 在下载了别人的Python源文件里面有rst ...

  10. vid = two 切开 分开 - 两个眼睛 还有看的含义 - 词根

    vid = two 切开 分开 - 两个眼睛 还有看的含义 - 词根 vi = wo acs 构词