java并发编程实战-第三章-对象的共享

441 阅读5分钟

3.1可见性

  • 首先我们需要知道的是,java的线程都有自己独立的缓存,线程之间进行共享变量的交互是通过自身和缓存和主存的交互实现的。
  • 如果线程的每次更改缓存都刷入主存,主存每次被一个线程的缓存修改,都通知所有的线程刷新自身的缓存的话,那样就太不经济了。
  • 由于1和2,就会产生一个现象:当线程1修改了一个共享变量之后,线程2获取的共享变量还是更改前的值。即线程1更改共享变量并没有刷入主存,或者线程2并没有去主存中获取到新的共享变量,或以上两者皆有
  • 为了解决内存可见性我们可以使用volatile关键字和同步这两种方式
  • 变量的读取,指令重排序

失效数据

非原子的64位操作

当读取一个非volatile类型的long变量时,如果对该变量的读操作和写操作在不同的线程中执行,那么很可能读取到某个值的高32位和另外一个值的低32位

加锁和可见性

加锁的含义不仅互斥,还包括内存可见性

volatile变量

  • 用来确保变量的更新操作通知到其他线程
  • 编译器与运行时都会注意到这个变量时共享的,不会进行重排序
  • volatile不会被缓存在寄存器或者其他处理器不可见的地方,读取变量时,总能返回最新的写入值

满足所有条件,才应该使用volatile

  • 对变量的写入不依赖变量的当前值,只有单个线程更新变脸的值
  • 该变量不会与其他状态一起纳入不变形条件中
  • 在访问时不需要加锁

3.2发布与溢出

一般自定义类需要考虑发布(构造方法), juc的类已经处理过了

发布:是对象能够在当前作用域之外的代码中使用

  • 将对象的引用存储到公共静态域。
  • 在非私有方法中返回引用。
  • 在发布某对象的时候可能会间接发布本不想发布的对象,如一个private的数组,一旦被发布,其中储存的对象也会被发布

溢出:不应该发布的被发布了

类对自己的内部状态进行了同步操作,如果发布出去可能会破坏封装性,并使程序难以维护不变性条件 一个对象在尚未准备好就将它发布出去——溢出。

  • “内部类发布也会引发溢出”,因为只有当对象通过构造函数返回之后,才处于稳定状态。这种发布会导致this溢出。
  • “即使在构造函数的最后一行发布也会有该问题”,指令重排序可能会引发一些奇怪的问题。而且该引用已经不是null了,但是内容还没有初始化完毕也有可能。
  • “不要让this在构造期溢出!”

常见错误

  • 1.在构造函数中创建并启动线程 这个时候线程已经获得了this的引用(即使是隐式的,因为该Runnable或者Thread是所属对象的内部类),this引用几乎总是被新线程所共享。 所以在构造函数中创建线程没有错误,但是不要在构造函数中启动它。
  • 2.注册一个内部类,使用this方法 可以使用静态工厂和私有构造函数来解决这个问题。

3.3线程封闭

数据仅在单线程中被访问,即数据不共享。 几种方式:

特定的方法

例如在一个线程中进行 读取修改写入,其他线程中进行读取

栈封闭

引用在局部变量中 注意不要让当前线程中的对象从所在线程溢出!

java的ThreadLocal

使用ThreadLocal可以做到线程隔离,每个线程都有自己单独的一个区域保存变量。

3.4不变性对象

  • 不可变对象满足下列条件:
    1. 所有域是final的,域内部的域也是final的
    2. 所有域不可改变
    3. this没有在构造的时候逸出

final域

使用volatile类型来发布不可变对象

3.5安全发布

不正确的发布:正确的对象被破坏

导致其他县城看到尚未创建完的对象

不可变对象与初始化安全

安全发布的常用模式

  1. 在静态初始化对象引用,因为JVM的类加载过程中是同步的
  2. 对对象引用使用volatile或AtomicReference
  3. 将对象引用放入final域中
  4. 对对象引用加锁

案例:

  • 将一个键值对放入HashTable,syhronizedMap或者ConcurrentMap中
  • 静态构造的对象 public static Holder holder= new Holder(42)

事实不可变对象

程序不会去修改,例如Date虽然是可变的,但是放入了同步的HashMap中,且不会修改,那么就认为是不可变的对象

可变对象

  • 不仅要保证发布时的状态可见性
  • 每次访问时同样需要使用同步来确保后需修改操作的可见性

小结

  • 不可变对象可以通过任意机制来发布()
  • 事实不可变对象必须通过安全方式来发布
  • 可变对象必须通过安全方式来发布,并且是线程安全的或者由某个所保护起来

安全的共享对象

在发布一个对象的时候需要明确指出该对象的多线程共享规则:

  1. 是线程封闭?:只能由一个线程拥有
  2. 是只读共享?:只能并发读
  3. 是线程安全共享?:类内部实现了同步,可以随意使用
  4. 是保护对象?:类内部没有实现同步,需要使用者在外部同步 原文地址: www.victor123.cn/2018/04/15/…