你一个想法,我一个想法,我们交流后,一个人有两个想法。如果您不能简单地解释它,那么您对它的理解还不够好。连同技术文章,Github实践精选,方便大家阅读。无法理解volatile和synchronized的区别,他的问题可以归纳为:volatile和synchronized在处理什么问题上相对等价?为什么说volatile是synchronized弱同步的方式呢?除了可见性问题,volatile还解决什么问题?我该如何在两者之间做出选择?如果你不能回答以上问题,说明你对两者的区别还有些模糊。本文将通过图文来谈谈他们之间微妙的关系。大家都听说过【天上一日,地下一年】。假设CPU执行一条普通指令需要一天,那么CPU读写内存就要等一年。受限于【木桶原理】,在CPU看来,内存的效率降低了程序的整体性能。为了解决这个缺点,硬件同学也用了我们常见的软件提速策略——使用缓存Cache(其实就是硬件同学给软件同学挖的坑)JavaMemoryModel(JMM)CPU加缓存给用内存平衡速度差异。这个增加还是好几层。这时候内存的缺点就没有那么明显了,CPU很高兴。但是它带来了很多问题。见上图。每个核心都有自己的一级缓存(L1Cache),有些架构还有一个所有核心共享的二级缓存(L2Cache)。使用缓存后,当一个线程要访问一个共享变量时,如果共享变量存在于L1中,则不再逐级访问主存。因此,通过这种方式,弥补了访问内存慢的缺点。具体来说,线程读写共享变量的步骤如下:将共享变量从主内存复制到自己的工作内存中。处理完成后,将变量值更新回主存。假设主存中有一个共享变量X,其初始值为0,线程1先访问变量X。应用上面的步骤是这样的:在L1和L2中找不到变量X,直到在主存中找到变量X复制到L1和L2,在L1中把X的值改成1,再写回主存一层一层。这时,在线程1的眼里,X的值是这样的:接下来,线程2也按照上面的步骤访问了变量X,在L1中并没有找到变量X。在L2中找到变量X。将变量从L2复制到L1,将L1中X的值修改为2,逐层写回此时在主存中,在线程2的眼中,X的值如下:结合刚才的两个操作,当线程1再次访问变量x时,我们看看有什么问题:此时如果线程1再次设置x=1写入,会覆盖掉线程2的结果x=2,同一个共享变量,线程得到的结果是不一样的(线程1眼里x=1;线程2眼里x=2),这就是为什么共享变量内存是不可见的问题。如何补洞?今天的两位主角登场,不过在讲解volatile关键字之前,先说说大家最熟悉的synchronized关键字。当synchronized遇到线程不安全问题时,你会习惯性地想到使用synchronized关键字来解决问题,不管这个方法合理不合理,先看看synchronized关键字是如何解决上述共享变量内存可见性问题的clearfromthememory,从主存读取[Exit]synchronized块的内存语义,将synchronized块中共享变量的修改刷新到主存,二话不说,往下看volatilevolatile变量声明时asvolatile时:当一个线程[读取]一个共享变量时,它首先清除本地内存中的变量值,然后从主内存中获取最新的值。当线程[写入]共享变量时,并不会把值缓存到寄存器或者其他地方(就是现在所谓的“工作内存”),而是会把值刷新回主存,感觉换汤不换药。你是对的。所以,在使用synchronized或者volatile的时候,多线程操作共享变量的步骤就变成了这样:简单的说,就是不再引用L1和L2中的共享变量的值,而是直接访问主存来获取更多实际的。上面的例子publicclassThreadNotSafeInteger{/***sharedvariablevalue*/privateintvalue;publicintgetValue(){returnvalue;}publicvoidsetValue(intvalue){this.value=value;}}经过前序分析,明显有很大上述代码中共享变量值存在隐患。尝试对其进行一些修改,先使用volatile关键字改造:publicclassThreadSafeInteger{/***sharedvariablevalue*/privatevolatileintvalue;publicintgetValue(){returnvalue;}publicvoidsetValue(intvalue){this.value=value;}}然后使用用于转换public的synchronized关键字classThreadSafeInteger{/***sharedvariablevalue*/privateintvalue;publicsynchronizedintgetValue(){returnvalue;}publicsynchronizedvoidsetValue(intvalue){this.value=value;}}这两个结果完全一样,可以看到共享变量数据[当前]解决方案在性问题上,两者是等价的。如果synchronized和volatile完全等价,那就没必要设计两个关键字了。继续看例子@Slf4jpublicclassVisibilityIssue{privatestaticfinalintTOTAL=10000;//即使像下面这样加上volatile关键字修改也解决不了问题,因为没有解决原子性问题privatevolatileintcount;publicstaticvoidmain(String[]args){VisibilityIssuevisibilityIssue=newVisibilityIssue();Threadthread1=newThread(()->visibilityIssue.add10KCount());Threadthread2=newThread(()->visibilityIssue.add10KCount());thread1.start();thread2.start();try{thread1.join();thread2.join();}catch(InterruptedExceptione){log.error(e.getMessage());}log.info("countvalue:{}",visibilityIssue.count);}privatevoidadd10KCount(){intstart=0;while(start++
