理解Java多线程

本文首发于“雨夜随笔”公众号,欢迎关注。

理解Java多线程,让我们从关键字volatile说起。

内存模式

了解过操作系统知识的知道,计算机的每条指令都是在CPU中执行。而执行的过程就涉及到数据的读取和写入。程序运行产生的临时数据当然都是保存在内存中,但是从内存中读取数据和往内存中写入数据的速度和CPU执行速度相比要慢的很多。所以为了提高运行效率,CPU中也就有了高速缓存。

这个意思就是说,当程序运行时,会将需要的数据从内存中拷贝到CPU中的高速缓存中,当CPU进行运算时将数据从高速缓存中读取和写入。运算结束后再将高速缓存中的数据刷新到内存中。

这个就涉及到多线程中可能出现的问题了,那就是每个线程都有自己的高速缓存。当读取数据进行分别计算后,最终刷新到内存中的数据可能并不是我们想象中的值。

i = i + 1

如果有两个线程执行这段代码,假设初始值是0,我们希望两个线程执行完后变成2.但是实际情况可能不是如此,存在一种情况:初始时,两个线程分别读取i的值存入自己的高速缓存中,然后线程1执行后,将最新值1刷新到内存中,然后线程2的高速缓存中i的值还是0,执行后将结果1刷新到内存中,最终结果是1,而不是2。

这个就是著名的缓存一致性问题。为了解决这个问题,人们试着提出很多方案,通常来说有两种方案:

1. 通过在总线加LOCK锁的方式

2. 通过缓存一致性协议(CCP)

而这两种是硬件产商采取的方案。但是总线加锁的方式会阻碍其他CPU访问内存,造成效率低下。而通过缓存一致性协议,虽然保存了一致性,但是无法保证实时性。

并发编程

并发编程中,需要保证三个事情:原子性,可见性和有序性。所以在并发编程中会出现这三个方面的问题。

原子性

原子性是指一个操作或者多个操作要么全部执行并且执行的过程不会被其他因素打断,要么就全部不执行。

所以在设计并发编程时,首先要考虑需要拆分成多线程的逻辑是不是原子性的,如果是的,就尽量不要设计成多线程。这一点我就犯了个错误,比如有一个操作是发信息给另一个服务,如果成功后把信息保存到数据库中。刚开始我觉得这两个可以分开,毕竟通过网络发送和存储信息到数据库中都是挺耗时的操作。但是其实这个想法是错误的,因为这个操作是原子性操作,如果发送不成功,那么就没有必要存储到数据库中。换言之,存储信息需要依赖网络发送的结果。两个要成功都成功,要失败就都失败。所以为了保证原子性,尽量不用多线程。

可见性

可见性是值多个线程访问同一个变量时,一个线程修改这个变量后,其他线程能够立即得到修改后的值。

上面说的 i=i+1 就存在这个问题,线程1修改后并不能保证线程2一定能够获取到修改后的值。

有序性

有序性是指程序执行的顺序按照代码的先后顺序执行。这点对于Java来说不是件容易的事情。因为JVM在真正执行代码的时候会进行指令重排序。因为为了提高程序运行效率,会对数据代码进行优化。虽然不保证语句的执行顺序和代码顺序一致,但是会保证最终的结果是预期的值。

而上面的保证只在单线程中实现,多线程访问时,准确性就有可能出错。

综上所述,并发编程想要保证程序正确执行,要同时保证原子性、可见性和有序性。

Java内存模型

Java虚拟机为了屏蔽硬件和操作系统的内存访问差异,达到程序在各种平台都能达到一致的内存访问效果,定义了一种Java内存模型(JMM)。

Java内存模型定义了程序变量的访问规则,所有的变量都是存在主存中,然后每个线程有自己的工作内存。线程对变量的所有操作都必须在自己的工作内存中进行,不能直接对主存进行操作,并且不能访问其他线程的工作内存。

那么Java是如何保证原子性、可见性和有序性呢?

原子性

在Java中,对基本数据类型的读取和赋值都是原子性的,这点由Java进行保证。而要实现更大规模的原子性操作,需要通过synchronized和Lock来进行实现。因为这两个关键词能够保证统一时刻只要一个线程访问执行代码块,那么就可以保证原子性。

可见性

这一点就说到了我们一开始提到的volatile关键字。Java提供了这个关键字以保证共享变量在修改时可以立即刷新到主存中,以便其他线程读取时可以获取最新值。

当然synchronized和Lock也可以保证可见性。

有序性

上面说到Java指令重排序会影响多线程的准确性,所以volatile也保证了一定的有序性,这就涉及了Java的排序规则:

程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作

1. 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作

2. 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作

3. volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作

4. 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C

5. 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作

6. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生

7. 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行

8. 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始

这些规则来自《深入理解Java虚拟机》,主要是为了保证重排序不会对结果产生影响。

Volatile

那么就正式来说以volatile关键字,volatile能够保证两点:

1. 不同线程对变量操作的可见性,一个线程修改了某个变量的值后,其他线程能够立即可见。

2. 禁止对该代码进行指令重排序。

但是从上面的介绍来看,volatile并不能保证原子性,这是因为如果一个线程只是读取了变量,并没有修改,而其他线程修改变量并不能刷新该线程已经读取的变量。所以如果源操作不是原子性的,那使用volatile也无法保证是原子性。

实现方式

下面这段话摘自《深入理解Java虚拟机》:

“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”

lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

2)它会强制将对缓存的修改操作立即写入主存;

3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

总结

Java多线程编程时,要时时考虑保证原子性、可见性和有序性。能够保证这三个,那么多线程的很多问题都可以得到化解。而volatile关键字的使用可以在某些场景中避免直接使用synchronized和Lock带来的效率问题。

参考:

https://www.cnblogs.com/dolphin0520/p/3920373.html

https://zhuanlan.zhihu.com/p/29138099



©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容