第五章节 - 跨线程共享内存

跨线程共享内存 - 章节
 

介绍跨线程共享内存 - 小节
 

线程间的沟通
线程需要共享它们的数据
比让值可见更复杂
值也必须正确:
1> 缓存[读取陈旧值]的问题
2> 编译器“欺骗”问题
 

好的设计是关键
意识到这一点很重要
1> 需要在设计阶段考虑如何共享数据
2> 没有计划地编写代码会导致困难
Bugs可以是:
1> 微妙的
2> 很难找到
3> 只是偶尔出现


将收获哪些...
不正确地同步数据的问题涵盖在四个例子中
如何安全地将不可变对象发布到线程
同步化在Java内存模型中得到了保证
1> java语言规范(第17章)



线程安全和java内存模型 - 小节


线程安全的
代码在多线程环境中正确执行


什么是共享状态?
程序数据
1> 已经存储
2> 可以更新
潜在的可共享:
1> 对其他线程可见
2> 静态变量、类数据、组合类数据、数组项、集合项
3> 不是局部变量


公布的数据
向其他线程公开的数据
必须遵循Java内存模型中的指导原则
1> 或者承担后果[微小的bug]



数据发布正确吗?
最后区域安全吗?
1> 是的,但请稍后注意
知道的数据正确地发布
1> 需要检查程序
2> 引用中包含的所有数据必须正确地发布


Java内存模型
听到这个定义:
1> 这会让你感到困惑吗?


Java内存模型
"Java内存模型描述了Java编程语言中的线程如何通过内存进行交互。"
——维基百科

‘The Java Memory Model defines a set of guarantees which, when applied to a program, ensure memory interactions between threads occur in a specified deterministic fashion.’


数据同步
当我们在一个线程上写一个值时
1> 我们想知道这个值在被另一个读取时是否正确可见
数据正确同步的
1> 意味着正确的可见性
不要与synchronized关键字混淆


示例:


共享非同步数据的问题示例 - 小节


伪代码约定
例如:
1> L1 - 表示局部变量L1
2> S2 - 表示共享变量S2
3> S2.X - S2的X字段
4> 1.2 - 线程1,语句2
变量以默认值开始


执行顺序
在单线程代码中,只有一个
在多线程代码中,执行顺序取决于:
1> 调度器
2> 处理器
3> 线程之间的相互作用


 

示例2:


代码重新排序
编译器,JVM或处理器可以重新排序代码
1> 让它执行得更快
单线程时不要注意它
1> 除非使用调试器
多线程会有害吗


优化是特定于平台的
Bug可能只是偶尔出现在生产系统上
尽管进行了广泛的开发测试


在行动中捕捉Bug
尝试使用调试器进行日志记录或单步执行?
但这种影响:
1> 执行顺序,时间,缓存内容,优化
打印更改线程所做的观察
1> Bug可能会消失


Heisenbugs
很难检测到
导致奇怪的bug报告
当你试图观察它们时,它们就消失了



使用Volatile解决数据同步问题 - 小节


数据竞争
不同的执行顺序是可能的
1> 不知道会发生什么
当读取未同步的共享可变数据导致意外或不正确的值时,我们有一场数据竞争。


数据同步
将volatile关键字添加到共享变量定义中
volatile有几个影响,在java面试中会被问到


在c/c++上使用volatile(但不是java!)
不缓存
在多线程条件下没有保证


在Java中的Volatile
Volatile函数一:
任何线程读取都会看到最新值
1> 机制未指定


Volatile变量
在伪代码中使用v表示volatile
例如vS1 - 共享变量S1是不稳定的
但最后不能是volatile
数组和对象引用可以标记为volatile
1> 不影响内容


 

Voletaile阻止优化
Volatile函数2:
1> 表示值可以在线程之间共享
2> 阻止基于程序顺序的优化


Voletile和内存关卡
Volatile函数3:
在线程之间同步数据
1> 通过安装内存关卡


内存关卡的定义
一种屏障指令,使中央处理器(CPU)或编译器对屏障指令前后发出的内存操作强制执行顺序约束


内存关卡的效果
一个volatile变量的写入器的内存状态是可见的
必须至少对相同变量的任何读者可见
至少指在写的时间点或以后的时间点
1> 很难/不可能准确地推断出处于什么状态


在Java中的Volatile
Volatile函数4:
防止某些重新排序(如下)

代码重新排序的可能性

改编自JSR-133烹饪书的编译器作家由道格李


Inconsistencies and Broken Invariants
状态,我们可以看到一个volatile读取:
1> 在前一个volatile write_or_later
可能只意味着更新对象的某些字段
1> Inconsistent/broken invariants
解决方案:
1> 正确发布对象
2> 互斥锁


 

Volatile的缺点
影响性能
1> 最新值必须对其他核心可见
不保证数据一致性


发布对象以共享数据 - 小节


发布对象
一个线程执行更新
1> 发布对其他线程的更改
2> 其他线程使用更新后的数据


 

 

最后一个变量
如果对象发布正确
- Reference to 'this'not allowed to escape during construction
其他线程只会看到初始化的最终值
- 不需要担心数据竞争
发布不可变对象比同意不更改它们要好


Publishing and Libraries 
问题:
- 多线程行为的文档?
- 源代码可用?
- 可能在读取操作时在引擎盖下发生突变
- 实现随着升级而变化,建议存储结果而不是发布库对象


Publishing in Practice 
它的工作原理,但是……
- 需要好的设计
- 清楚/简单/严格的工作实践
- 开发人员知识渊博的
- 照顾了
比总是检查同步要好



其他Java内存模型保证 - 小节


线程创建/死亡的保证
创建线程时
- 它至少可以看到状态创建者在创建时可以看到
当线程已死,而另一个线程调用iaAlive()或join()时
- 至少可以看到线程在退出时看到的状态


Word Tearing
数组的修改在哪里影响相邻的元素
如:
- 给定一个字节数组
- 如果处理器必须写完整的单词
- 相邻元素可能受到同步问题的影响
在Java中不是问题


64 Bit Primitives
长/双线程不安全(除非易挥发-volatile)
- 可分为两个32位操作读取/写入
- 因此可以读取从未赋值的值(只执行一个操作)
或者使用AtomicLong、Long或Double包装器
64位引用总是安全的



跨线程共享内存的摘要 - 小节


Thread Safety,Synchronization and Data Races
由于缓存、编译器和处理器优化
- 在单线程模式下工作的代码不能在多线程条件下工作
- 其中共享可变状态
- 需要正确同步,以避免数据竞争


线程安全、同步和数据竞争
标记变量volatile的作用:
- 随时查看最新版本
- 防止有害的优化
- 安装限制重新排序的内存栅栏
- 同步线程的‘世界观'


如何安全地发布对象
JMM担保:
- 线程的创建
- 线程死亡
- No word tearing
64位原始问题
- 引用是安全的


 

 

 


 

 

 

 

 

 

 

 


 

 

 


 


 

 

 

 

 

 


 

 


 


 

 

 


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值