热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

打工人!肝了这套多线程吧!壹

开篇闲扯    一年又一年,年年多线程。不论你是什么程序员,都逃脱不了多线程并发的魔爪。因为它从盘古开天辟地的时候就有了,就是在计算机中对现实世界的一种抽象。因此,放轻松别害怕,肝

开篇闲扯

    一年又一年,年年多线程。不论你是什么程序员,都逃脱不了多线程并发的魔爪。因为它从盘古开天辟地的时候就有了,就是在计算机中对现实世界的一种抽象。因此,放轻松别害怕,肝了这系列的多线程文章,差不多能吊打面试官了(可别真动手...)。

并发症

    并发问题,曾经在单核单线程的机器上是不存在的(不是不想,是做不到)。假如把计算机看成一个木桶,那么跟我们Java开发人员关系最大的就是CPU、内存、IO设备。这三块木板发展至今,彼此之间也形成了较大的性能差异。CPU的核心数线程数在不断增多,内存的速度却跟不上CPU的步伐,同理IO设备也没能跟上内存的步伐。于是就加缓存,经过科学论证三级缓存最靠谱,于是就有了常见的CPU三级缓存。然后前辈们再对操作系统做各类调度层面的深度优化,通过软硬兼施的手法,使得软件与硬件的完美结合,才有如今繁荣的互联网。而我们不过是在这座城市里的打工人罢了。
言归正传,本文将分别说明在并发世界里的“三宗罪”:可见性原子性有序性


罪状一:可见性

前文中有说到CPU的发展经历了从单核单线程到现在的多核心多线程,而内存的读写性能却供应不上CPU的处理能力,于是就增加了缓存,至于前文中提到的三级缓存为什么是三级,不在本文讨论范围,有兴趣自己看去。。。

为什么会有可见性问题?

    在单核心时代,所有的线程都是交给一个CPU串行执行,因此不论有多少线程都是排队执行,也就不会形成线程A与B同时竞争target变量的竞争状态,如图一。
打工人!肝了这套多线程吧!壹
    来到多核心多线程时代,每颗CPU都有各自的缓存,如果多个线程分别在不同的CPU上运行,且需要同时操作同一个数据。而每颗CPU在处理内存中的数据时,会先将目标数据缓存到CPU缓存中。这时候CPU们各干各的,也不管目标值有没有被其他CPU修改过,自己在缓存中修改后不管三七二十一就写回去,这肯定是不行的啊兄弟...,而这就是我们Java中常说的数据可见性问题,再追根溯源就是:CPU级别的缓存一致性协议。后边文章会详细解释(别问具体时间,问了就是明天)。

可见性问题怎么解决?

    这个简单,如果仅仅是解决可见性,那就Volatile关键字用起来(也不是万能的,慎重考虑),它会将共享变量数据从线程工作内存刷新到主存中,而这个关键字的实现基础是Java规范的内存模型,注意,这里要和JVM内存模型区分开,两者是不一样的东西。那么Java内存模型又是什么样的,为啥设计这个内存模型,有哪些好处?下篇详细解释!本文就先放一张简单的图:
打工人!肝了这套多线程吧!壹


罪状二:原子性

    大家都知道CPU的运行时间是分片进行的,可能CPU这段时间在执行我写的if-else,下一时刻由于操作系统的调度当前线程丢失时间片,又执行其他线程任务去了(呸!渣男)。打断了我当前线程的一个或者多个操作流程,这就是原子性被破坏了,也就是多线程无锁情况下的ABA问题。跟我们期望的完全不一样啊,还是看图说话:
打工人!肝了这套多线程吧!壹
    解释一下就是:想要得到temp为2的结果,但是只能得到1,原因就是运行A线程的CPU干别的去了,而这时候B线程所在的CPU后发制A,抢先完成了++的操作并写回内存,但是A不知道,还傻傻的以为它的到的是temp的初恋,又傻傻的写会去,然后就心态崩了呀!偷袭~(出错)


罪状三:有序性

    如果说原子性问题是硬件工程师挖的坑(CPU别切换多好),那有序性就妥妥的是软件工程师下了老鼠夹子(夸张了啊,其实都是为了效率)。之所以存在有序性问题,完全是编译大神们对我们的关爱,知道我们普通Coder对性能的要求是能跑就行。

    因此,在Java代码在编译时期动了手脚,比如说:锁消除、锁粗化(需要进行逃逸性分析等技术手段)或者是将A、B两段操作互换顺序。但是,所有的这一切都不能影响源码在单线程执行情况下的最终结果,即as-if-serial语义。这是个很顶层的协议,不论是编译器、运行时状态还是处理器都必须遵守该语义。这是保证程序正确性的大前提。当然,编译器不仅仅要准守as-if-serial语义,还要准守以下八大规则--Happens-Before规则(八仙过海各显神通):

什么是Happens-Before规则?

    一段程序中,前面运行后的结果,对后面的操作来说均可见。即:不论怎么编译优化(编译优化的文章以后会写,关注我,全免费)都不能违背这一指导思想。不能忘本

规则名称 解释
程序顺序规则 在一个线程中,按照程序的顺序,前面的操作先发生于后续的操作
volatile变量规则 对volatile变量进行写操作时,要优先发生于对这个变量的读操作,可以理解为禁止指令重排但实际不完全是一个意思
线程start()规则 很好理解,线程的start()操作要优先发生于该线程中的所有操作(要先有鸡才能有蛋)
线程join()规则 线程A调用线程B的join()并成功返回结果时,线程B的任意操作都是先于join()操作的。
管理锁定规则 在java中以Synchronized为例来说就是加解锁操作要成对且解锁操作在加锁之后
对象终结规则 一个对象的初始化完成happen—before它的finalize()方法的开始
传递性 即A操作先于B发生,B先于C发==>A先于C发生

:文章里所有类似“先于”、“早于”等词并不严谨不能和Happens-Before划等号,只是这样说更好理解,较为准确的含义是:操作结果对后者可见。

其实,总结来说就是JMM、编译器和程序员之间的关系。

JMM对程序员说:你按顺序写,执行结果就是按照你写的顺序执行的,有BUG就是你自己的问题。
程序员:好的,听你的!
JMM对编译器说:你不能随便改变程序员的代码顺序,我都跟他承诺写啥是啥了,别搞错了。
编译器:收到!(可我还是想优化,我不影响你不就行了,这优化我做定了,奥利给!)

于是就有了这些规则,而对于我们CRUD Boy来说都是不可见的,了解一下就OK!

感谢各位看官!

更多文章请扫码关注或微信搜索Java栈点公众号!

打工人!肝了这套多线程吧!壹


推荐阅读
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 深入理解Java虚拟机的并发编程与性能优化
    本文主要介绍了Java内存模型与线程的相关概念,探讨了并发编程在服务端应用中的重要性。同时,介绍了Java语言和虚拟机提供的工具,帮助开发人员处理并发方面的问题,提高程序的并发能力和性能优化。文章指出,充分利用计算机处理器的能力和协调线程之间的并发操作是提高服务端程序性能的关键。 ... [详细]
  • 生产环境下JVM调优参数的设置实例
     正文前先来一波福利推荐: 福利一:百万年薪架构师视频,该视频可以学到很多东西,是本人花钱买的VIP课程,学习消化了一年,为了支持一下女朋友公众号也方便大家学习,共享给大家。福利二 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • ejava,刘聪dejava
    本文目录一览:1、什么是Java?2、java ... [详细]
  • 初识java关于JDK、JRE、JVM 了解一下 ... [详细]
  • 一面自我介绍对象相等的判断,equals方法实现。可以简单描述挫折,并说明自己如何克服,最终有哪些收获。职业规划表明自己决心,首先自己不准备继续求学了,必须招工作了。希望去哪 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了软件测试知识点之数据库压力测试方法小结相关的知识,希望对你有一定的参考价值。 ... [详细]
  • HashMap的相关问题及其底层数据结构和操作流程
    本文介绍了关于HashMap的相关问题,包括其底层数据结构、JDK1.7和JDK1.8的差异、红黑树的使用、扩容和树化的条件、退化为链表的情况、索引的计算方法、hashcode和hash()方法的作用、数组容量的选择、Put方法的流程以及并发问题下的操作。文章还提到了扩容死链和数据错乱的问题,并探讨了key的设计要求。对于对Java面试中的HashMap问题感兴趣的读者,本文将为您提供一些有用的技术和经验。 ... [详细]
  • linux进阶50——无锁CAS
    1.概念比较并交换(compareandswap,CAS),是原⼦操作的⼀种,可⽤于在多线程编程中实现不被打断的数据交换操作࿰ ... [详细]
  • 开发笔记:Python之路第一篇:初识Python
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Python之路第一篇:初识Python相关的知识,希望对你有一定的参考价值。Python简介& ... [详细]
  • Java编程思想一书中第21章并发中关于线程间协作的一节中有个关于汽车打蜡与抛光的小例子(原书的704页)。这个例子主要展示的是两个线程如何通过wait ... [详细]
author-avatar
只是遇不到他_740
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有