简介二狗:二胖,你昨天请假了,是不是又去面试了?二胖:不说了,我出去试水,看看现在能不能找到工作,顺便出去找个打击,然后才能静下心来好好学习。二狗:那被打的呢?你知道你是什么水平,傻笑。二胖:底子太差,让我回去等通知。我得好好学习了,别跟你废话了。二狗:问你什么问题就这样打你?让我们一起重播,让我做好准备。二胖:嗯,既然你这么好奇,那我给你大概说说吧。您可以移到小板凳上并小心。我要开始我的表演了。接下来,二胖的第一面开始了。采访者:二胖是吧?让我先自我介绍一下。二胖:好的,我叫二胖。我来自长沙。我今年25岁,从事java开发快3年了。现在我在XX公司XX事业部工作,担任高级java开发工程师,主要负责XX系统。....面试官:好的,我看到你的简历说你精通并发编程。你能告诉我你在并发编程中知道哪些关键字吗?二胖:这可不仅仅是考我synchronized和volatile。我很擅长这个。我是故意记下来的。synchronized是java提供的关键字。主要可以保证原子性和顺序。它的底层主要是通过Monitor实现的。的。volatile也是java的关键字,主要作用是保证可见性。...这里省略1000字。面试官:八股文你背得很好。说了这么多,我们来试试看,写一个双重检查锁(dcl)的单例。让我看看。二胖:我从屁股口袋里掏出一支笔,默默地写着。面试官:你说了volatile关键字和synchronized关键字。synchronized可以保证原子性、顺序和可见性。但是volatile只能保证顺序和可见性。那么,我们再来看看双重检查锁的单例实现。已使用同步。为什么我们需要易失性?这个volatile能去掉吗?二胖:我想想,好像确实可以去掉。采访者:今天的采访到此结束。如有消息,工作人员稍后会与您联系。谢谢你今天来面试。二胖很郁闷,回谷歌找这个问题。stackoverflow上也有这个问题。看来这个问题不是只有我一个人不知道吗?看来以上故事纯属虚构。如有雷同,请以本文为主。synchronized的有序性?我们来看看没有volatile修饰的单例:1publicclassSingleton{2privatestaticSingletonsingleton;3privateSingleton(){}4publicstaticSingletongetSingleton(){5if(singleton==null){6synchronized(Singleton.class){7if(singleton==null){8singleton=newSingleton();9}10}11}12返回单例;13}14}上面的代码看起来还好吗?首先我们来看看这行代码做了什么。singleton=newSingleton()我们可以将上面的过程简化为3步:①JVM为对象分配一块内存M。②初始化内存M上的对象。③将内存M的地址复制到单例变量中。该步骤有两种执行顺序,可按①②③或①③②执行。当我们按照①③②的顺序执行时,假设有两个线程ThreadA和ThreadB同时请求Singleton.getSingleton方法:正常情况下,第一步按照①②③的顺序执行:ThreadA进入第8行,executessingleton=newSingleton()初始化对象(根据对象初始化流程①②③)并执行。第二步:ThreadB进入第5行判断单例不为空(第一步已经初始化),直接返回单例。第三步:获取这个对象做其他操作。所以看起来没有问题。那么如果按照①③②的步骤初始化了对象,我们再来看一下:第一步:ThreadA进入第8行,执行singleton=newSingleton()并执行。①JVM为对象分配一块内存M。③将内存地址复制到单例变量中。第2步:此时ThreadB直接进入第5行,发现单例不为空,然后直接跳转到第12行获取单例,返回执行操作。此时ThreadB得到的单例对象是一个半成品对象,因为还没有针对这个对象进行初始化(②还没有执行)。第三步:因此,ThreadB获取到的对象在执行该方法时,可能会出现异常。至于为什么会这样列出来?《Java 并发编程实战》提到会有synchronized和novolatile的DCL(doublechecklock):线程可能看到引用的当前值,但是对象的状态值确实很少invalid,也就是说线程可以看到对象处于无效或错误状态。说白了ThreadB可以获得一个已经有引用但还没有分配内存资源的对象。如果要解决按照①②③的顺序创建对象的问题,其实只要在第二行加上volatile修饰即可解决指令重排。约定的synchronized不保证顺序吗?volatile的有序性?同步性不足以保证指令重排吗?如何定义顺序?《深入理解Java虚拟机第三版》提到Java程序中的自然顺序可以用一句话来概括:如果在这个线程中观察,所有的操作都是自然有序的。如果一个线程观察另一个线程,则所有操作都是无序的。前半句是指“在线程中出现串行的语义”,后半句是指“指令重排”和“工作内存与主存同步延迟”的现象。synchronized的有序性是两个持有同一个锁的synchronizedblock只能串行进入,即加锁的内容必须被多个线程依次执行,但是内部同步代码还是会重新排序,这样Blocks是顺序可见的从一个块到另一个块。volatile的有序性是通过插入内存屏障来保证指令按顺序执行的。在执行之前的指令之前,将不会有后续指令运行。就是保证编译器在优化的时候不会把指令弄乱。synchronized不保证指令重排。**本文参与SegmentFault随笔《如何“反杀”面试官?如果您正在阅读,欢迎您加入。**最后,由于本人孤陋寡闻,难免有错误。如果您发现错误,请留言指出给我,我会改正。如果您觉得文章还不错,您的转发、分享、欣赏、点赞、评论就是对我最大的鼓励。感谢您的阅读,非常欢迎并感谢您的关注。站在巨人的肩膀上摘苹果:https://stackoverflow.com/que...https://juejin.cn/post/684490...
