前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >简直了,被“Java并发锁”问题追问到自闭...

简直了,被“Java并发锁”问题追问到自闭...

作者头像
程序员老猫
发布于 2024-04-03 11:28:44
发布于 2024-04-03 11:28:44
9200
代码可运行
举报
文章被收录于专栏:程序员老猫程序员老猫
运行总次数:0
代码可运行

分享是最有效的学习方式。 博客:https://blog.ktdaddy.com/

故事

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

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

上次“【ThreadLocal问出花】”,小帅其实也有吸取教训得,这次对于多线程的问题还是做了很多准备的...可是没想到这次的结果居然也还是这样。

Java中的锁了解吧?介绍一下吧”,面试官不紧不慢地问到。

“乐观锁、悲观锁、公平锁、非公平锁,然后平时咱们的synchronized是基于.....”小帅把知道的所有关于锁的基本都回答了一遍。

面试官对他笑了笑,“就这些吗?还有呢?比如自旋锁、可重入锁、独占锁....并且说一下你的理解,或者聊一下使用场景的优劣吧。”

“额.....以前好像看到过...”小帅语无伦次地回答到。

“嗯,行吧,之前的那些答得可以的,不过一会我这边有个会,要不今天咱们就聊到这里?回去等通知吧...”

Java中让人眼花缭乱的锁你是否真的一一清楚了?

试问这样一个大而宽的问题,大家能够总结全吗,如果让各位来回答,能否回答完全呢?

我们在实际的并发编程中,常常遇到多个线程访问一个共享变量的情况,当同时对共享变量进行读写操作的时候,就会产生数据不一致的情况。为了保证资源获取的有序性,我们就常常会用到并发锁。

那么接下来咱们就来聊聊这些Java并发锁的理解吧。我们将从以下这些方面来一起回顾一下Java中的并发锁。

概要

乐观锁和悲观锁:线程是否锁住同步资源

大家其实对乐观锁和悲观锁听说的比较多一些,所以咱们就先来聊聊这两种类型的锁。这两种类型的锁,本质区分是要看线程是否锁住同步资源。

先来看一下悲观锁。悲观锁就是每次去拿数据的时候都会认为别人会修改数据,所以在读取数据的时候都会上锁。这样就会导致线程临时阻塞。

悲观锁

再来看一下乐观锁,乐观锁就是每次在拿数据的时候都假设别人不会修改数据,所以都不会进行上锁;只有在更新数据的时候才去判断之前有没有别的线程更新了这条数据。如果没有更新,那么当前线程会自己修改数据并且写入成功。如果数据已经被其他线程更新了,那么会报错或者自动重试,例如下图。

乐观锁

上述两种锁,并没有优劣之分。只是看相关的场景然后分别去使用。

乐观锁:适用于写少读多的场景。因为不用上锁,释放锁,省去了锁的开销,从而提升了吞吐量。

悲观锁:适用于写多读少的场景。因为线程竞争激烈,如果使用乐观锁会导致线程不断进行重试,反而降低吞吐量。

共享锁和独占锁:多个线程是否共享同一把锁

并发场景下,如果多个线程能够共享一把锁,那么就是所谓的共享锁,如果不能,那么则为独占锁(其他命名:排他锁或者独享锁)。

共享锁指锁可以被多个线程持有。如果一个线程对数据加上共享锁,那么其他线程只能对数据再加共享锁,不能加独占锁。另外的共享锁的线程只能读数据,不能修改数据。如下图。

共享锁

独占锁是指锁一次只能被一个线程持有,如果一个线程对数据加上独占锁,那么其他的线程则不能对该数据再加任何类型的锁。如果一个线程获取独占锁,那么则该线程既可以读数据又可以修改数据。

独占锁

对于独占锁来说,大家比较熟悉的就是synchronized和J.U.C包中的Lock实现类。

大家可能也听说过互斥锁,其实互斥锁就是独占锁的一种常规实现。

读写锁是共享锁的一种具体实现。读写锁管理一组锁,一个是只读的锁,一个是写锁。

读锁可以再没有写锁的时候被多个线程同时持有,而写锁是独占的,于此同时写锁的优先级要高于读锁,一个获得了读锁的线程必须能看到前一个释放的写锁更新的内容。

读写锁和互斥锁对比,其性能更高,每次只有一个写线程,但是有多个线程可以并发读。

读写锁

例如,ReentrantReadWriteLock。具体伪代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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。伪代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Lock lock = new ReetrantLock(false);

ReentrantLock默认为非公平锁,设置为true的时候表示公平锁。当设置为false的时候表示非公平锁。

可重入锁和不可重入锁:同一个线程中多个流程是否能够获取同一把锁。

如果一个线程中的多个流程能够获取同一把锁,那么我们就叫该所为可重入锁,反之则为不可重入锁。咱们光看文字描述的话可能比较抽象。我们看一下下图。

在Java中可重入锁一般有ReentrantLock,其命名就已经很明确了。另外的synchronized也是可重入锁。可重入锁的优势是可以一定程度上避免死锁发生。上面的示意图转换为如下demo:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public synchronized void methodA() {
  methodB()
}

public synchronized void methodB() {
  methodC()
}

public synchronized void methodC(){
  doSomeThing()
}

自旋锁或者自适应自旋锁:线程锁定同步资源失败,如该线程没有被阻塞场景下发生

如果一个线程锁住同步资源失败,但是又希望这个线程不被阻塞,那么此时咱们就可以使用自旋锁或者自适应自旋锁。自旋锁指线程没有获得锁的情况下不被挂起,而是执行一个忙循环。那么这个忙循环的话就成为自旋。如下:

自旋锁

目的:减少线程被挂起的概率,因为线程被挂起和唤醒也是消费资源。

Java中AtomicInteger类就有自旋的操作,如下源代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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底层其实将锁的粒度进一步细化了,存在了分段锁的概念等等。

总结

这些让人眼花缭乱的锁,如果面试官问到的话,大家是否能够说出一二呢?相信看完上面的解释,大家心里多多少少也有数了吧。当然关于最后一点其他分类的锁,老猫没有展开。有兴趣的小伙伴可以自行查阅一下这些分类。

我是老猫,资深研发老鸟,让我们一起聊聊技术,聊聊职场,聊聊人生。

创作不易,求个点赞、关注、在看三连,感谢支持。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-04-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员老猫 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
图解Java中那18 把锁
举个生活中的例子,假设厕所只有一个坑位了,悲观锁上厕所会第一时间把门反锁上,这样其他人上厕所只能在门外等候,这种状态就是「阻塞」了。
肉眼品世界
2021/09/27
2490
图解Java中那18 把锁
5000字 | 24张图带你彻底理解21种并发锁
乐观锁是一种乐观思想,假定当前环境是读多写少,遇到并发写的概率比较低,读数据时认为别的线程不会正在进行修改(所以没有上锁)。写数据时,判断当前 与期望值是否相同,如果相同则进行更新(更新期间加锁,保证是原子性的)。
悟空聊架构
2020/09/03
8300
简单了解下Java中锁的概念和原理
Java提供了很多种锁的接口和实现,通过对各种锁的使用发现理解锁的概念是很重要的。
codetrend
2024/06/29
1310
万字长文带你了解Java中锁的分类
Java中的锁是一种多线程编程中的同步机制,用于控制线程对共享资源的访问,防止并发访问时的数据竞争和死锁问题。通过使用锁机制,可以实现数据的同步访问,确保多个线程安全地访问共享资源,从而提高程序的并发性能。
索码理
2023/08/21
5640
万字长文带你了解Java中锁的分类
JAVA并发编程系列(4)一文看懂全部锁机制
曾几何时,面试官问:java都有哪些锁?小白,一脸无辜:用过的有synchronized,其他不清楚。面试官:回去等通知!
拉丁解牛说技术
2024/09/09
2630
Java中常用的锁介绍
  乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作。   Java中的乐观锁基本都是通过CAS操作实现的,CAS是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。
程序员云帆哥
2022/05/12
3430
Java中15种锁的介绍
在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类。介绍的内容如下:
Java团长
2019/04/25
3910
深入理解java并发锁
确保线程安全最常见的做法是利用锁机制(Lock、sychronized)来对共享数据做互斥同步,这样在同一个时刻,只有一个线程可以执行某个方法或者某个代码块,那么操作必然是原子性的,线程安全的。
Java宝典
2021/01/14
4300
深入理解java并发锁
Java 种15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁等等
在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类。介绍的内容如下:
美的让人心动
2019/06/15
2.5K0
不可不说的Java“锁”事
Java提供了种类丰富的锁,每种锁因其特性的不同,在适当的场景下能够展现出非常高的效率。本文旨在对锁相关源码(本文中的源码来自JDK 8)、使用场景进行举例,为读者介绍主流锁的知识点,以及不同的锁的适用场景。
美团技术团队
2018/11/16
7380
Java中锁概念总结
Java开发过程中会涉及很多锁,这些锁的作用各不相同,本篇对这些锁的概念及作用进行了整理。 公平锁和非公平锁 公平锁:多个线程申请获取同一个锁,按照线程的申请顺序,排队获取锁。公平锁的好处是等待的线程不会被饿死,相应的缺陷就是整体吞吐量很低、效率很低。使用new ReentrantLock(true)可以构造一个公平锁。 非公平锁:多个线程申请获取同一个锁,获取锁的顺序不按照申请顺序,抢占式的获取。非公平锁的好处是整体效率很高,但是可能会使有些线程一致在等待,造成饿死。使用Synchronized、new
JavaQ
2018/04/08
6570
一网打尽Java中锁的分类
可重入锁是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。 ReentrantLock和synchronized都是可重入锁。可重入锁的一个好处是可一定程度避免死锁。
全菜工程师小辉
2019/08/16
4710
关于Java的那些“锁”事
Java中的分很多种类,按照场景的不同、特性的不同等分为了很多类,下面就来讲讲Java中锁的概念:
Qwe7
2022/02/16
4560
我去了,一篇文章,看懂锁???
Lock和synchronized,是最常见的锁,都可以达到线程安全的目的,功能常见不同
Joseph_青椒
2023/08/28
2100
我去了,一篇文章,看懂锁???
并发编程之各种锁的简介
一、公平锁/非公平锁 公平锁是指多个线程按照申请锁的顺序来获取锁。 非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。 对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。 对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。 二、可重入锁
lyb-geek
2018/03/27
1.2K0
Java中的锁分类
在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类。介绍的内容如下:
哲洛不闹
2019/03/01
9610
Java中的锁分类
史上最全 Java 中各种锁的介绍
在计算机科学中,锁(lock)或互斥(mutex)是一种同步机制,用于在有许多执行线程的环境中强制对资源的访问限制。锁旨在强制实施互斥排他、并发控制策略。 锁通常需要硬件支持才能有效实施。这种支持通常采取一个或多个原子指令的形式,如"test-and-set", "fetch-and-add" or "compare-and-swap"”。这些指令允许单个进程测试锁是否空闲,如果空闲,则通过单个原子操作获取锁。
java金融
2020/08/04
3780
相关推荐
图解Java中那18 把锁
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验