- 线程之间的通信
- 线程的通信是指线程之间以何种机制来交换信息。在编程中,线程之间的通信机制有两种,共享内存和消息传递。
- 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信,典型的共享内存通信方式就是通过共享对象进行通信
- 在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显示进行通信,在java中典型的消息传递方式就是wait()和Notify()
- 线程之间的同步
- 同步是指程序用于控制不同线程之间操作发生相对顺序的机制
- 在共享内存并发模型里,同步是显示进行的。程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行
- 在消息传递的并发模型里,由于消息的发送必须在消息的接收之前,因此同步是隐式进行的。
- java内存中的共享
- java内存中的重排序
- 编译器优化重排序
- 指令集并行重排序
- 内存系统重排序
- 重排序与依赖性
- 数据依赖性
- 控制依赖性
- as-if-serial
- 解决并发下的问题
- 内存屏障 :a,禁止重排序,b,强制刷出Cache
四种屏障类型:
1> LoadLoadBarriers :Load1.LoadLoad.Load2.Load2
2> StoreStoreBarriers :Store1.StoreStore.Store2
3> LoadStoreBarriers : Load1.StoreStore.Store2
4> StoreLoadBarriers :Store1.StoreStore.Load2 - 临界区(锁)
- 内存屏障 :a,禁止重排序,b,强制刷出Cache
- Happens-before
- 用Happens-before的概念来阐述操作之间的内存可见性。在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系。
- 两个操作之间具有happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行!happens-before仅仅要求前一个操作执行的结果对后一个操作可见,且前一个操作按顺序排在第二个操作之前
- Happens-before规则。无需任何同步手段就可以保证的:
1> 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作
2> 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁
3> volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读
4> 传递性:如果A happens-before B,且 B happens-before C,那么A happens-before C
5> start()规则:如果线程A执行操作ThreadB.start(),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作
6> join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作Happens-before于线程A从ThreadB.join()操作成功返回
7> 线程中断规则:对线程interrupt()方法的调用happens-before于被中断线程的代码检测到中断操作的发生
- volatile的内存语义:
- 可以把对volatile变量的单个读/写,看成是使用同一个锁对这个变量单个读/写操作做了同步
- volatile写的内存语义:当写一个volatile变量时,JMM会把该线程对应的本地内存中的所有共享变量值刷新到主内存
- 当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量
- JMM对volatile的内存屏障插入策略
1> 在每个volatile写操作的前面插入一个StoreStore屏障,在每个volatile写操作的后面插入一个StoreLoad屏障
2> 在每个volatile读操作的后面插入一个LoadLoad屏障,在每个读操作的后面插入一个LoadStore屏障
- 锁的内存语义
- 当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中
- 当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视保护的临界区代码必须从主内存中读取共享变量
- final的内存语义
-
重排序规则
1> 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序
2> 初次读一个包含final的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序
3> final域为引用类型增加如下规则:在构造函数内对一个final引用的对象的成员域的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序 -
final语义在处理器中的实现
1> 会要求编译器在final域的写之后,构造函数return之前插入一个StoreStore屏障
2> 读final域的重排序要求编译器在读final域的操作前面插入一个LoadLoad屏障
-
- volatile的实现原理
- 有volatile变量修饰的共享变量进行写操作的时候会使用CPU提供的LOCK前缀指令
- 将当前处理器缓存的数据写回到系统内存
- 这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效
- Synchronized的实现原理
- 使用monitorenter和monitorexit指令实现的
1> monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处
2> 每个monitoryenter必须有对应的monitorexit与之配对
3> 任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态 - 了解各种锁状态(级别从低到高)
1> 无锁状态
2> 偏向锁状态:大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得所的代价更低而引入了偏向锁。无竞争时不需要进行CAS操作来加锁和解锁
3> 轻量级锁状态:通过CAS操作来加锁和解锁
4> 重量级锁状态
- 使用monitorenter和monitorexit指令实现的
- 自旋锁:通过循环CAS操作拿到锁,是拿锁的一种方式
网友评论