当Synchronized遇到这玩意儿,有个大坑,要注意

多线程环境下@Transactional与synchronized的相爱相杀:事务提交时机的陷阱 本文深入剖析了Spring多线程环境下@Transactional与synchronized联用时的经典陷阱。核心揭示了由于事务提交发生在锁释放之后,导致的数据不一致问题。文章通过实际案例,详细解释了事务提交时机与锁释放的间隙,并提供了手动管理事务和使用SERIALIZABLE隔离级别两种解决方案及其选型建议。 阅读详情

前几天在某技术平台上看到别人提的关于 Synchronized 的一个用法问题,我觉得挺有意思的,这个问题其实也是我三年前面试某公司的时候遇到的一个真题,当时不知道面试官想要考什么,没有回答的特别好,后来研究了一下就记住了。

所以看到这个问题的时候觉得特别亲切,准备分享给你一起看看:

首先为了方便你看文章的时候复现问题,我给你一份直接拿出来就能跑的代码,希望你有时间的话也把代码拿出来跑一下:

public class SynchronizedTest {

    public static void main(String[] args) {
        Thread why = new Thread(new TicketConsumer(10), "why");
        Thread mx = new Thread(new TicketConsumer(10), "mx");
        why.start();
        mx.start();
    }
}

class TicketConsumer implements Runnable {

    private volatile static Integer ticket;

    public TicketConsumer(int ticket) {
        this.ticket = ticket;
    }

    @Override
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName() + "开始抢第" + ticket + "张票,对象加锁之前:" + System.identityHashCode(ticket));
            synchronized (ticket) {
                System.out.println(Thread.currentThread().getName() + "抢到第" + ticket + "张票,成功锁到的对象:" + System.identityHashCode(ticket));
                if (ticket > 0) {
                    try {
                        //模拟抢票延迟
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "抢到了第" + ticket-- + "张票,票数减一");
                } else {
                    return;
                }
            }
        }
    }
}

程序逻辑也很简单,是一个模拟抢票的过程,一共 10 张票,开启两个线程去抢票。

票是共享资源,且有两个线程来消费,所以为了保证线程安全,TicketConsumer 的逻辑里面用了 synchronized 关键字。

这是应该是大家在初学 synchronized 的时候都会写到的例子,期望的结果是 10 张票,两个人抢,每张票只有一个人能抢到。

但是实际运行结果是这样的,我只截取开始部分的日志:

截图里面有三个框起来的部分。

最上面的部分,就是两个人都在抢第 10 张票,从日志输出上看也完全没有任何毛病,最终只有一个人抢到了票,然后进入到第 9 张票的争夺过程。

但是下面被框起来的第 9 张票的争夺部分就有点让人懵逼了:

why抢到第9张票,成功锁到的对象:288246497
mx抢到第9张票,成功锁到的对象:288246497

为什么两个人都抢到了第 9 张票,且成功锁到的对象都一样的?

这玩意,超出认知了啊。

这两个线程怎么可能拿到同一把锁,然后去执行业务逻辑呢?

所以,提问者的问题就浮现出来了。

  • 1.为什么 synchronized 没有生效?

  • 2.为什么锁对象 System.identityHashCode 的输出是一样的?

为什么没有生效?

我们先来看一个问题。

首先,我们从日志的输出上已经非常明确的知道,synchronized 在第二轮抢第 9 张票的时候失效了。

经过理论知识支撑,我们知道 synchronized 失效,肯定是锁出问题了。

如果只有一把锁,多个线程来竞争同一把锁,synchronized 绝对是不会有任何毛病的。

但是这里两个线程并没有达成互斥的条件,也就是说这里绝对存在的不止一把锁。

这是我们可以通过理论知识推导出来的结论。

先得出结论了,那么我怎么去证明“锁不止一把”呢?

能进入 synchronized 说明肯定获得了锁,所以我只要看各个线程持有的锁是什么就知道了。

那么怎么去看线程持有什么锁呢?

jstack 命令,打印线程堆栈功能,了解一下?

这些信息都藏在线程堆栈里面,我们拿出来一看便知。

在 idea 里面怎么拿到线程堆栈呢?

这就是一个在 idea 里面调试的小技巧了,我之前的文章里面应该也出现过多次。

首先为了方便获取线程堆栈信息,我把这里的睡眠时间调整到 10s:

跑起来之后点击这里的“照相机”图标:

点击几次就会有对应点击时间点的几个 Dump 信息:

由于我需要观察前两次锁的情况,而每次线程进入锁之后都会等待 10s 时间,所以我就在项目启动的第一个 10s 和第二个 10s 之间各点击一次就行。

为了更直观的观察数据,我选择点击下面这个图标,把 Dump 信息复制下来:

复制下来的信息很多,但是我们只需要关心 why 和 mx 这两个线程即可。

这是第一次 Dump 中的相关信息:

mx 线程是 BLOCKED 状态,它在等待地址为 0x000000076c07b058 的锁。

why 线程是 TIMED_WAITING 状态,它在 sleeping,说明它抢到了锁,在执行业务逻辑。而它抢到的锁,你说巧不巧,正是 mx 线程等待的 0x000000076c07b058。

从输出日志上来看,第一次抢票确实是 why 线程抢到了:

从 Dump 信息看,两个线程竞争的是同一把锁,所以第一次没毛病。

好,我们接着看第二次的 Dump 信息:

这一次,两个线程都在 TIMED_WAITING,都在 sleeping,说明都拿到了锁,进入了业务逻辑。

但是仔细一看,两个线程拿的锁是不相同的锁。

mx 锁的是 0x000000076c07b058。

why 锁的是 0x000000076c07b048。

由于不是同一把锁,所以并不存在竞争关系,因此都可以进入 synchronized 执行业务逻辑,所以两个线程都在 sleeping,也没毛病。

然后,我再把两次 Dump 的信息放在一起给你看一下,这样就更直观了:

如果我用“锁一”来代替 0x000000076c07b058,“锁二”来代替 0x000000076c07b048。

那么流程是这样的:

why 加锁一成功,执行业务逻辑,mx 进入锁一等待状态。

why 释放锁一,等待锁一的 mx 被唤醒,持有锁一,继续执行业务。

同时 why 加锁二成功,执行业务逻辑。

从线程堆栈中,我们确实证明了 synchronized 没有生效的原因是锁发生了变化。

同时,从线程堆栈中我们也能看出来为什么锁对象 System.identityHashCode 的输出是一样的。

第一次 Dump 的时候,ticket 都是 10,其中 mx 没有抢到锁,被 synchronized 锁住。

why 线程执行了 ticket-- 操作,ticket 变成了 9,但是此时 mx 线程被锁住的 monitor 还是 ticket=10 这个对象,它还在 monitor 的 _EntryList 里面等着的,并不会因为 ticket 的变化而变化。

所以,当 why 线程释放锁之后,mx 线程拿到锁继续执行,发现 ticket=9。

而 why 也搞到一把新锁,也可以进入 synchronized 的逻辑,也发现 ticket=9。

好家伙,ticket 都是 9, System.identityHashCode 能不一样吗?

按理来说,why 释放锁一后应该继续和 mx 竞争锁一,但是却不知道它在哪搞到一把新锁。

那么问题就来了:锁为什么发生了变化呢?

谁动了我的锁?

经过前面一顿分析,我们坐实了锁确实发生了变化,当你分析出这一点的时候勃然大怒,拍案而起,大喊一声:是哪个瓜娃子动了我的锁?这不是坑爹吗?

按照我的经验,这个时候不要急着甩锅,继续往下看,你会发现小丑竟是自己:

抢完票之后,执行了 ticket-- 的操作,而这个 ticket 不就是你的锁对象吗?

这个时候你把大腿一拍,恍然大悟,对着围观群众说:问题不大,手抖而已。

于是大手一挥,把加锁的地方改成这样:

synchronized (TicketConsumer.class)

利用 class 对象来作为锁对象,保证了锁的唯一性。

经过验证也确实没毛病,非常完美,打完收工。

但是,真的就收工了吗?

其实关于锁对象为什么发生了变化,还隔了一点点东西没有说出来。

它就藏在字节码里面。

我们通过 javap 命令,反查字节码,可以看到这样的信息:

Integer.valueOf 这是什么玩意?

让人熟悉的 Integer 从 -128 到 127 的缓存。

也就是说我们的程序里面,会涉及到拆箱和装箱的过程,这个过程中会调用到 Integer.valueOf 方法。具体其实就是 ticket-- 的这个操作。

对于 Integer,当值在缓存范围内的时候,会返回同一个对象。当超过缓存范围,每次都会 new 一个新对象出来。

这应该是一个必备的八股文知识点,我在这里给你强调这个是想表达什么意思呢?

很简单,改动一下代码就明白了。

我把初始化票数从 10 修改为 200,超过缓存范围,程序运行结果是这样的:

很明显,从第一次的日志输出来看,锁都不是同一把锁了。

这就是我前面说的:因为超过缓存范围,执行了两次 new Integer(200) 的操作,这是两个不同的对象,拿来作为锁,就是两把不一样的锁(注意这里的程序是去掉了static)。

再修改回 10,运行一次,你感受一下:

从日志输出来看,这个时候只有一把锁,所以只有一个线程抢到了票。

因为 10 是在缓存范围内的数字,所以每次是从缓存中获取出来,是同一个对象。

我写这一小段的目的是为了体现 Integer 有缓存这个知识点,大家都知道。但是当它和其他东西揉在一起的时候因为这个缓存会带来什么问题,你得分析出来,这比直接记住干瘪的知识点有效一点。

但是...

我们的初始票是 10, ticket-- 之后票变成了 9,也是在缓存范围内的呀,怎么锁就变了呢?

如果你有这个疑问的话,那么我劝你再好好想想。

10 是 10,9 是 9。

虽然它们都在缓存范围内,但是本来就是两个不同的对象,构建缓存的时候也是 new 出来的:

为什么我要补充这一段看起来很傻的说明呢?

因为我在网上看到其他写类似问题的时候,有的文章写的不清楚,会让读者误认为“缓存范围内的值都是同一个对象”,这样会误导初学者。

总之一句话: 请别用 Integer 作为锁对象,你把握不住。

但是...

stackoverflow

但是,我写文章的时候在 stackoverflow 上也看到了一个类似的问题。

这个哥们的问题在于:他知道 Integer 不能做为锁对象,但是他的需求又似乎必须把 Integer 作为锁对象。

https://stackoverflow.com/questions/659915/synchronizing-on-an-integer-value

我给你描述一下他的问题。

首先看标号为 ① 的地方,他的程序其实就是先从缓存中获取,如果缓存中没有则从数据库获取,然后再放到缓存里面去。

非常简单清晰的逻辑。

但是他考虑到并发的场景下,如果有多个线程同一时刻都来获取同一个 id,但是这个 id 对应的数据并没有在缓存里面,那么这些线程都会去执行查询数据库并维护缓存的动作。

对应查询和存储的动作,他用的是 fairly expensive 来形容。

就是“相当昂贵”的意思,说白了就是这个动作非常的“重”,最好不要重复去做。

所以只需要让某一个线程来执行这个 fairly expensive 的操作就好了。

于是他想到了标号为 ② 的地方的代码。

用 synchronized 来把 id 锁一下,不幸的是,id 是 Integer 类型的。

在标号为 ③ 的地方他自己也说了:不同的 Integer 对象,它们并不会共享锁,那么 synchronized 也没啥卵用。

其实他这句话也不严谨,经过前面的分析,我们知道在缓存范围内的 Integer 对象,它们还是会共享同一把锁的,这里说的“共享”就是竞争的意思。

但是很明显,他的 id 范围肯定比 Integer 缓存范围大。

那么问题就来了:这玩意该咋搞啊?

我看到这个问题的时候想到的第一个问题是:上面这个需求我好像也经常做啊,我是怎么做的来着?

想了几秒恍然大悟,哦,现在都是分布式应用了,我特么直接用的是 Redis 做锁呀。

根本就没有考虑过这个问题。

如果现在不让用 Redis,就是单体应用,那么怎么解决呢?

在看高赞回答之前,我们先看看这个问题下面的一个评论:

开头三个字母:FYI。

看不懂没关系,因为这个不是重点。

但是你知道的,我的英语水平 very high,所以我也顺便教点英文。

FYI,是一个常用的英文缩写,全称是 for your information,供参考的意思。

所以你就知道,他后面肯定是给你附上一个资料了,翻译过来就是:Brian Goetz 在他的 Devoxx 2018 演讲中提到,我们不应该把 Integer 作为锁。

你可以通过这个链接直达这一部分的讲解,只有不到 30s秒的时间,随便练练听力:https://www.youtube.com/watch?v=4r2Wg-TY7gU&t=3289s

那么问题又来了?

Brian Goetz 是谁,凭什么他说的话看起来就很权威的样子?

Java Language Architect at Oracle,开发 Java 语言的,就问你怕不怕。

同时,他还是我多次推荐过的《Java并发编程实践》这本书的作者。

好了,现在也找到大佬背书了,接下来带你看看高赞回答是怎么说的。

前部分就不详说了,其实就是我们前面提到的那一些点,不能用 Integer ,涉及到缓存内、缓存外巴拉巴拉的...

关注划线的部分,我加上自己的理解给你翻译一下:

如果你真的必须用 Integer 作为锁,那么你需要搞一个 Map 或 Integer 的 Set,通过集合类做映射,你就可以保证映射出来的是你想要的明确的一个实例。而这个实例,就可以拿来做锁。

然后他给出了这样的代码片段:

就是用 ConcurrentHashMap 然后用 putIfAbsent 方法来做一个映射。

比如多次调用 locks.putIfAbsent(200, 200),在 map 里面也只有一个值为 200 的 Integer 对象,这是 map 的特性保证的,无需过多解释。

但是这个哥们很好,为了防止有人转不过这个弯,他又给大家解释了一下。

首先,他说你也可以这样写:

但这样一来,你就会多产生一个很小成本,就是每次访问的时候,如果这个值没有被映射,你都会创建一个 Object 对象。

为了避免这一点,他只是把整数本身保存在 Map 中。这样做的目的是什么?这与直接使用整数本身有什么不同呢?

他是这样解释的,其实就是我前面说的“这是 map 的特性保证的”:

当你从 Map 中执行 get() 时,会用到 equals() 方法比较键值。

两个相同值的不同 Integer 实例,调用 equals() 方法是会判定为相同的 。

因此,你可以传递任何数量的 "new Integer(5)" 的不同 Integer 实例作为 getCacheSyncObject 的参数,但是你将永远只能得到传递进来的包含该值的第一个实例。

就是这个意思:

汇总一句话:就是通过 Map 做了映射,不管你 new 多少个 Integer 出来,这多个 Integer 都会被映射为同一个 Integer,从而保证即使超出  Integer 缓存范围时,也只有一把锁。

除了高赞回答之外,还有两个回答我也想说一下。

第一个是这个:

不用关心他说的内容是什么,只是我看到这句话翻译的时候虎躯一震:

skin this cat ???

太残忍了吧。

我当时就觉得这个翻译肯定不太对,这肯定是一个小俚语。于是考证了一下,原来是这个意思:

免费送你一个英语小知识,不用客气。

第二个应该关注的回答排在最后:

这个哥们叫你看看《Java并发编程实战》的第 5.6 节的内容,里面有你要寻找的答案。

巧了,我手边就有这本书,于是我翻开看了一眼。

第 5.6 节的名称叫做“构建高效且可伸缩的结果缓存”:

好家伙,我仔细一看这一节,发现这是宝贝呀。

你看书里面的示例代码:

不就和提问题的这个哥们的代码如出一辙吗?

都是从缓存中获取,拿不到再去构建。

不同的地方在于书上把 synchronize 加在了方法上。但是书上也说了,这是最差的解决方案,只是为了引出问题。

随后他借助了 ConcurrentHashMap、putIfAbsent 和 FutureTask 给出了一个相对较好的解决方案。

你可以看到完全是从另外一个角度去解决问题的,根本就没有在 synchronize 上纠缠,直接第二个方法就拿掉了 synchronize。

看完书上的方案后我才恍然大悟:好家伙,虽然前面给出的方案可以解决这个问题,但是总感觉怪怪的,又说不出来哪里怪。原来是死盯着 synchronize 不放,思路一开始就没打开啊。

书里面一共给出了四段代码,解决方案层层递进,具体是怎么写的,由于书上已经写的很清楚了,我就不赘述了,大家去翻翻书就行了。

没有书的直接在网上搜“构建高效且可伸缩的结果缓存”也能搜出原文。

我就指个路,看去吧。

好了,那本文的技术部分就到这里啦。

下面这个环节叫做[荒腔走板],技术文章后面我偶尔会记录、分享点生活相关的事情,和技术毫无关系。我知道看起来很突兀,但是我喜欢,因为这是一个普通博主的生活气息。

深入理解Java wait()方法:从监视器锁到线程协作的实战解析 在Java并发编程中,线程间的协作与同步是核心基础。其底层依赖于监视器锁(Monitor Lock)机制,该机制通过对象头中的Mark Word实现锁状态的记录与竞争管理。理解这一原理对于构建高并发、高可用的系统至关重要,它能有效避免数据竞争和状态不一致问题。在实际工程实践中,wait()与notify()/notifyAll()方法是实现线程间条件等待与通知的关键原语,广泛应用于生产者-消费者、任务协调等经典场景。然而,若使用不当,极易引发IllegalMonitorStateException、虚假唤醒 阅读详情

相关推荐

PS/2鼠标接口协议

一、PS/2鼠标接口        PS/2接口用于许多现代的鼠标和键盘,由IBM最初开发和使用。物理上的PS/2接口有两种类型的连接器:5脚的DIN和6脚的MINI-DIN。        二、鼠标的接口协议原理             PS/2鼠标接口采用一种双向同步串行协议。即每在时钟线上发一个脉冲,就在数据线上发送一位数据。但是在相互传输中 ,主机是拥有总线控制权的

Ant's home 1万+

java基础Synchronized原理及逃逸分析

Synchronized用法锁升级无锁identity hashcodeagebiased locklock偏向锁轻量级锁重量级锁 用法 synchronized可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。 synchronized有三种应用方式: 作用于实例方法,当前实例加锁,进入同步代码前要获得当前实例的锁; 作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁; 作用于代码块,对括号里配置的对象加锁。 锁升级 在说锁升级前时先

练习时长1个月 457

abap中使用Screen painter为普通屏幕创建单选按钮组radiobutton组的过程

选择屏幕中,创建radiobutton组直接使用代码实现,如下所示 SELECTION-SCREEN BEGIN OF BLOCK BK1 WITH FRAME TITLE TEXT-001. PARAMETERS R1 RADIOBUTTON GROUP GR1. PARAMETERS R2 RADIOBUTTON GROUP GR1. PARAMETERS R3 RADIOBUTTON GR

charliefromkansas的博客 8300

JUC并发编程 Ⅱ -- 共享模型之管程(上)

本文涉及共享带来的问题、synchronized解决方案、变化的线程安全分析、Monitor、synchronized原理进阶等内容。

十八岁讨厌编程的博客 861

PRPD图谱分析从入门到精通:5种典型放电模式的识别手册

本文详细解析PRPD图谱分析技术,重点介绍电晕放电、悬浮放电等5种典型放电模式的识别方法。通过37组真实案例对比,提供关键识别区和诊断流程图模板,帮助工程师快速掌握变电站设备状态监测的核心技能,提升绝缘缺陷诊断准确率。

weixin_29214199的博客 466

企业数字化转型的理论指南:构建未来企业的关键策略与实践路径

数字化转型已经成为当今企业发展和市场竞争的核心驱动力,而在这一过程中,企业架构(EA)发挥着至关重要的作用。这本白皮书《世界级企业架构:建立和发展EA能力的领导者方法》提供了深入的理论指导,为企业如何通过企业架构实现有效的数字化转型提供了清晰的路径。本篇文章将从,解读该书的核心内容,揭示企业在数字化转型过程中应该如何通过EA的构建和实施,驱动组织变革与技术创新。

AZone架构院的博客 3172

synchronized没能锁住方法???学到了新知识

昨天,同事告诉我生产系统上竟然出现了两个一样的申请号,我表示十分震惊,申请号是系统中唯一标示(不是表主键),怎么会重复呢??? 我去找了一下生成申请号的代码,第一眼就觉得没问题,明明是加了synchronized的方法,怎么会生成一样的申请号呢,我甚至开始怀疑synchronized到底是不是真的能保证线程同步了。并且我们也不是集群,就一个tomcat,完全没道理。 我看到数据库里这两条申请的创建时间是一模一样的,那还真是无巧不成书,也就是说这两个也业务员正好在同一时间点击了按钮。 然后找了两个同事,

九尾狐的yi巴的博客 1671

【Settings随记:二】一级菜单,二级菜单的添加与隐藏,添加辅助功能

首先会去调用父类的onCreatePreferences(Bundle savedInstanceState, String rootKey)方法,然后调用addPreferencesFromResource(R.xml.assistant_function)方法(这个方法是父类的)加载二级菜单的xml配置文件,实现创建二级菜单,这个配置文件路径在res/xml/assistant_function.xml。controller:该item的控制器,控制它的内容展示,是否可用,也可以控制它的点击事件等。

镇元子的博客 1594

关于synchronized在操作数据库时候用的地方

synchronized 这个关键字,如果是多个操作一起操作数据库,尤其是涉及到增删改的时候,一组操作一定要加这个关键字,否则在会出现幻读或者虚度。

gbh666666的博客 1149

《YOLO11魔术师专栏》专栏介绍 & 专栏目录

【原创自研模块】【多组合点优化】【注意力机制】【主干篇】【neck优化】【卷积魔改】 【block&多尺度融合结合】【损失&IOU优化】【上下采样优化】【小目标性能提升】【前沿论文分享】【训练实战篇】

①答疑群聊服务;②YOLO大模型知识问答系统;③计算机视觉论文生成智能体; 1万+

svn Synchronizing: No changes found.

编译器:Eclipse 问题:出现在与资源库有同步时,大概意思就是资源库位置发生了变化 解决方案: preferences----general 里面的沟取消就完成了 大功告成!!! 点个赞再走呗!!!...

qq_41572507的博客 1829

【华为OD机试真题】29、组成最大数 | 机试真题+思路参考+代码解析 (C++、Java、Py、C语言、JS)

题目描述 >小组中每位都有一张卡片,卡片上是6位内的正整数,将卡片连起来可以组成多种数字,计算组成的最大数字

KJ.JK 2418

当Synchronized遇到这玩意儿,有个大坑,要注意!

在某技术平台上看到别人提的关于 Synchronized 的一个用法问题,我觉得挺有意思的,这个问题其实也是我三年前面试某公司的时候遇到的一个真题,当时不知道面试官想要考什么,没有回答的特别好,后来研究了一下就记住了。所以看到这个问题的时候觉得特别亲切,准备分享给你一起看看:程序逻辑也很简单,是一个模拟抢票的过程,一共 10 张票,开启两个线程去抢票。票是共享资源,且有两个线程来消费,所以为了保证线程安全,TicketConsumer 的逻辑里面用了 synchronized 关键字。

cxytony的博客 946

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Java中synchronized同步的个人见解

Java中synchronized同步的个人见解

qq_30021219的博客 492

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

关于synchronized失效wenti

国庆前公司的商城做了一波活动,导致平时流水五六千的商城在9月30日的一天流水暴涨到近40W,随之而来的就是一波大坑。 简单介绍一下业务场景,一个商城系统,一个发码系统。商城是卖电子券的,用户在商城购买卡券,商城系统就会调用发码系统给客户进行发码(短信形式),然后把发送的码返回给商城系统存入卡包。我们有部分商城的券码是购买第三方平台的券码(如星巴克,肯德基等),购买后把对应的码导入到发码平台。...

qq_39654396的博客 1278

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

Java开发实习面试八股文核心知识点全解析

Java后端开发面试中,基础原理的掌握程度往往决定成败。从JVM内存模型到并发编程,从HashMap底层实现到Spring核心机制,这些知识点不仅是面试八股文中的高频考点,更是构建健壮应用的技术基石。理解对象生命周期、锁升级过程、索引选择等底层逻辑,能帮助开发者快速定位线上问题,优化系统性能。在真实的工程实践中,无论是排查内存溢出、设计高并发接口,还是优化数据库查询,都离不开对这些原理的深刻洞察。这份针对Java开发实习面试的八股文整理,系统梳理了最高频的考点与答题框架,助你建立完整的复习脉络。

weixin_33937499的博客 403

ThreadPoolTaskExecutor避坑指南:为什么我的双重检查锁还是报错?

本文深入剖析了在高并发场景下使用ThreadPoolTaskExecutor时,经典双重检查锁模式失效的根源。文章指出,仅使用synchronized无法保证线程安全,必须结合volatile关键字来防止指令重排序,并强调务必调用initialize()方法完成Spring线程池的完整初始化,才能构建真正健壮的懒加载单例。

p5l2m9n4o6q的博客 475

Java volatile关键字:从CPU缓存一致性到并发编程实战

在并发编程中,内存可见性和指令重排序是常见的挑战。Java内存模型(JMM)定义了线程与主内存的交互规则,而现代CPU的多级缓存架构可能导致线程间数据不一致。volatile关键字通过插入内存屏障,确保变量的写操作立即刷新到主内存,并使其他线程的缓存失效,从而解决可见性问题。同时,它禁止编译器和处理器对指令进行重排序,保证有序性。这一机制在状态标志位、一次性安全发布等场景中具有重要技术价值。本文结合缓存失效案例,深入剖析volatile的原理与典型应用,帮助开发者避免多线程环境下的隐蔽Bug。

weixin_33730836的博客 484
上一篇: 关于高并发,我想聊一聊。
下一篇: 聊聊接口性能优化的11个小技巧
JavaMonsterr
博客等级 码龄4年 211粉丝 481原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值