Java Concurrency 学习

并发机制:

  • 内存共享: Java, C#, Pthread, win32
  • 消息传递:JVM:Scale, Google:Go, Ericsson:Erlang

特点:

  • 内存共享:显示同步,隐式通信
  • 消息传递:显示通信,隐式同步

Java并发=显示同步+隐式通信

Java并发处理对象(Shared+Mutable对象):

  • 共享变量(需要处理):实例域,静态域,数组元素
  • 局部变量(不需处理):局部变量,方法定义参数,异常处理参数

还有一个危险来源于64位类型long和double的非原子操作。

并发编程面对的问题:

  • visibility
  • ordering
  • atomicity

指令重排序 Reordering

为了提高指令处理效率,存在以下三个级别的指令重排序:

  • 编译器级(软): just-in-time compiler and bytecode compiler
  • 指令级并行级(硬): Processer
  • 内存系统级(硬): memory hierarchy

重排序的首要条件:保证数据依赖关系。

硬件(CPU)重排序类型,第一种所有平台都支持,后三种选择性支持:

  • 写读(都支持)
  • 写写
  • 读写
  • 读读

为了在硬件重排序的情况下也能保证内存可见性,需要在两个指令中间插入内存屏障指令。 内存屏障指令会保证第一个操作的对象写到主存,并把其它线程中引用该对象的内存设为失效。

Java简单内存模型

Java Memory Model(JMM)

synchronized 内置锁/监视器锁

  • 锁保护的代码块
  • 锁的对象:方法所在的this对象(一般方法)或Class对象(静态方法)

对象内置锁称为Monitor,每个对象一个。

Java获取锁的操作的粒度是线程,而不是调用。 就是说同时只能有一个线程进入同一个锁,其它想获得此锁的线程会阻塞。 并且这个线程对于此锁是可重入的,便于支持锁方法的继承。 每个锁记录了获取次数和一个所有者线程。

之所以每个对象都有一个内置锁,只是为了免去显示创建锁对象。 常见的加锁策略是把所有可变状态封装到对象内部,利用内置锁对访问这些可变状态的线程进行同步。

通过显示地加synchronized锁,JMM会隐式通过通信机制保证:

  • 释放锁时,临界区的共享变量从本底内存同步到主内存
  • 获取锁时,临接区的共享变量被置为无效,即再从主内存同步到本底内存

synchronized会引起上下文线程切换(?)。

key ideas about synchronized:

  • Conflicting Access: at least one of accesses is a write
  • Happen-Before Relationship: If one action happen before another, then the first is visible to and ordered before the second.Including:
    • Each action in a thread happens before every subsequent action in that thread.
    • An unlock on a monitor happens before every subsequent lock on that monitor.
    • A write to a volatile field happens before every subsequent read of that volatile.
    • A call to start() on a thread happens before any actions in the started thread.
    • All actions in a thread happen before any other thread successfully returns from a join() on that thread.
    • If an action a happens before an action b, and b happens before an action c, then a happens before c.

volatile

volatile是轻量级同步机制,也是由JMM隐式通信机制保证。 访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞。

  • volatile变量写时:从本地内存同步到主内存,标记其它线程变量失效
  • volatile变量读时:如果失效,从主内存同步到本地内存

volatile防止了指令重排序。

volatile常用于某个操作完成,发生中断或状态的标志。 但是volatile变量只能保证可见性,不能保证原子性。 如volatile的count++的原子性就不能保证。

final

除了final限制了成员的不变性之外, final成员和普通成员的区别是:final成员必需在新对象构造函数中赋值完成。

需要这点保证的直接原因是新对象的引用和初使化(构造函数)指令可能会重排序。 也就是说可能新对象的引用先被赋于某个变量,然后再使用构造函数初使化新对象成员, 直到第一次使用新对象成员之前完成即可。 这种情况在Java和CPP中都存在。 这样如果令一个线程在构造函数完成之前访问新对象的成员,就会是一个无效值。

因此,final成员在初次获得对象的引用之后(也就是构造函数完成之后),就保证了能在令一个线程正确读取到final值。这两个顺序是有保证的。

Reference

  • JLS Chapter 17
  • JSR 133