02对象的共享

对象的共享

最低安全性?

可能会读到一个错误的值,但至少是前一个线程设置的,而不是随机值。

非 volatile 的 long和double的特殊性:

无法保证最低安全性

可见性问题:

synchronized 和 volatile 的区别?

  1. volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
  2. volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
  3. volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
  4. volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
  5. volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化

volatile 适用场景:

  1. 对变量的写入不依赖当前值(没有原子性问题),或者能保证只在单线程中写入(volatile 也可以防止重排序,volatile 提供给了较弱的同步)
  2. 该变量不参与其他变量的不变性条件判断中
  3. 访问时不用加锁

对象发布有什么用?

使对象能够在当前作用域之外使用,如

  1. 将对象的引用保存到其他代码可以访问的地方
  2. 使用非私有方法返回对象的引用
  3. 将引用传递到其他类的方法中

对象发布需要注意什么?

发布后的对象是在单线程中被访问还是在多线程中被访问,都是未知的,因此难以维持不变性,会有线程安全的问题。当一个不应该发布的对象被发布时,就称为对象逸出

常见的对象逸出案例:

  1. 不希望被更改的对象被发布(间接或直接),后续状态将无法预知
  2. 建造方法中传递this(内部类 或 新建线程并作为参数传递),但此时对象还未完成构建
  3. jvm中对象的创建过程是非原子的,会导致引用指向一个未建构完成的对象(在普通方法中初始化后发布)
  4. 由于工作内存副本导致的不可见性

问题1解决方案:

  1. 线程封闭

    如果不希望对象被发布,为了避免错误的编码导致对象被发布,可以使用线程封闭的对象:

    线程封闭是指不共享数据,对象只在单线程中被使用,没有线程安全性问题

    1. ad-hoc封闭:没有任何技术支持,只能由开发人员人为的去保持数据没有发生共享,比较困难
    2. 栈封闭:常见的局部变量
    3. ThreadLocal
  2. 使用不可变对象

    不可变对象:

    1. 对象创建完之后其状态就不能修改
    2. 对象的所有域都是 final 类型(final 除了能保存字段值或引用不被更改,还能保证初始化过程的安全性)
    3. 对象只在构造函数中初始化,且被正确的构造(创建期间没有 this 的逸出) 

    不可变对象一定是线程安全的,配合 volitalie 使用效果更佳

问题2解决方案:

不要在构造方法中传递this,可以私有化构造器,提供公有的工厂方法。

问题3解决方案:

  1. final关键词可以确保初始化安全性(避免重排序)
  2. 在静态初始化函数中初始化一个对象引用

问题4解决方案:

使用 volitalie 或者 同步锁 实现可见性

可变对象的安全发布:

要安全的发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见,一个正确构造(构造函数中无this逸出)的对象可以通过以下的方式安全地发布:

  1. 在静态初始化函数中初始化一个对象引用
  2. 对象的引用保存到volatile类型的域或者AtomaticReferance对象中(volatile 也可以防止重排序,volatile 提供给了较弱的同步)
  3. 将对象的引用保存至某个正确构造对象的final类型域中
  4. 将对象的引用保存到一个由锁保护的域中

一旦对象引用对其他线程可见,则其final成员也必须正确的赋值。所以说借助于final,就如同你对对象的创建访问加锁了一般,天然的就保障了对象的安全发布。

注意,如果由于不可见性,对象引用未被其他线程观察到,

本文标题:02对象的共享

文章作者:Sun

发布时间:2020年08月04日 - 11:08

最后更新:2020年10月12日 - 19:10

原始链接:https://sunyi720.github.io/2020/08/04/Java并发编程/02对象的共享/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。