对象的共享
最低安全性?
可能会读到一个错误的值,但至少是前一个线程设置的,而不是随机值。
非 volatile 的 long和double的特殊性:
无法保证最低安全性
可见性问题:
synchronized 和 volatile 的区别?
- volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
- volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
- volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
- volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
- volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
volatile 适用场景:
- 对变量的写入不依赖当前值(没有原子性问题),或者能保证只在单线程中写入(volatile 也可以防止重排序,volatile 提供给了较弱的同步)
- 该变量不参与其他变量的不变性条件判断中
- 访问时不用加锁
对象发布有什么用?
使对象能够在当前作用域之外使用,如
- 将对象的引用保存到其他代码可以访问的地方
- 使用非私有方法返回对象的引用
- 将引用传递到其他类的方法中
对象发布需要注意什么?
发布后的对象是在单线程中被访问还是在多线程中被访问,都是未知的,因此难以维持不变性,会有线程安全的问题。当一个不应该发布的对象被发布时,就称为对象逸出
常见的对象逸出
案例:
- 不希望被更改的对象被发布(间接或直接),后续状态将无法预知
- 建造方法中传递this(内部类 或 新建线程并作为参数传递),但此时对象还未完成构建
- jvm中对象的创建过程是非原子的,会导致引用指向一个未建构完成的对象(在普通方法中初始化后发布)
- 由于工作内存副本导致的不可见性
问题1解决方案:
线程封闭
如果不希望对象被发布,为了避免错误的编码导致对象被发布,可以使用线程封闭的对象:
线程封闭是指不共享数据,对象只在单线程中被使用,没有线程安全性问题
- ad-hoc封闭:没有任何技术支持,只能由开发人员人为的去保持数据没有发生共享,比较困难
- 栈封闭:常见的局部变量
- ThreadLocal
使用不可变对象
不可变对象:
- 对象创建完之后其状态就不能修改
- 对象的所有域都是 final 类型(final 除了能保存字段值或引用不被更改,还能保证初始化过程的安全性)
- 对象只在构造函数中初始化,且被正确的构造(创建期间没有 this 的逸出)
不可变对象一定是线程安全的,配合 volitalie 使用效果更佳
问题2解决方案:
不要在构造方法中传递this,可以私有化构造器,提供公有的工厂方法。
问题3解决方案:
- final关键词可以确保初始化安全性(避免重排序)
- 在静态初始化函数中初始化一个对象引用
问题4解决方案:
使用 volitalie 或者 同步锁 实现可见性
可变对象的安全发布:
要安全的发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见,一个正确构造(构造函数中无this逸出)的对象可以通过以下的方式安全地发布:
- 在静态初始化函数中初始化一个对象引用
- 对象的引用保存到volatile类型的域或者AtomaticReferance对象中(volatile 也可以防止重排序,volatile 提供给了较弱的同步)
- 将对象的引用保存至某个正确构造对象的final类型域中
- 将对象的引用保存到一个由锁保护的域中
一旦对象引用对其他线程可见,则其final成员也必须正确的赋值。所以说借助于final,就如同你对对象的创建访问加锁了一般,天然的就保障了对象的安全发布。
注意,如果由于不可见性,对象引用未被其他线程观察到,