JAVA并发编程学习笔记------对象的可见性及发布逸出
一、非原子的64位操作:
当线程在没有同步的情况下读取变量时,可能会得到一个失效值,但至少这个值是由之前某个线程设置的值,而不是一个随机值,这种安全性保证被称为最低安全性。最低安全性适用于绝大多数变量,但存在一个例外:非volatile类型的64位数值变量(double,long),Java内存模型要求,变量的读取和写入操作都必须是原子操作,但对于非volatile型的long,double变量,JVM允许将64位的读操作或写操作分解为两个32位的操作,当读取一个非volatile类型的long变量时,如果对该变量的读操作和写操作在不同的线程中执行,那么很可能会读取到某个值的高32位和另一个值的低32位。
因此,即使不考虑失效数据问题,在多线程中使用共享且可变的long,double等类型的变量也是不安全的,除非用关键字volatile来声明它们,或者用锁保护起来。
二、volatile变量
当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量时共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
volatile局限在于:它通常用于某个操作完成、发生中断或者状态的标志,其语义不足以确保递增操作的原子性,除非你能确保只有一个线程对变量执行写操作。
当且仅当满足以下条件时,才应该使用volatile变量:
1)对变量的写入操作不依赖变量的当前值,或者能确保只有单个线程更新变量的值;
2)该变量不会与其他状态变量一起纳入不变性条件中;
3)在访问变量时不需要加锁。
加锁机制既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性。
三、对象的发布逸出
如下示例代码:
public class ThisEscape {
public ThisEscape(EventSource source) {
source.registerListener{
new EventListener()(){
public void onEvent(Event e){
doSomething(e);
}
}
}
}
}
这个类不是安全的,因为当ThisEscape发布EventListener时,也隐含的发布了ThisEscape实例本身,因为这个内部类的实例中包含了对ThisEscape实例的隐含应用,因此,当从对象的构造函数中发布对象时,只是发布了一个尚未构造完成的对象。即使发布对象的语句位于构造函数的最后一行也是如此。在构造函数中创建线程并没有错误,但最好不要立即启动它,而是通过一个start或initialize方法来启动,例如,可通过如下工厂方法来防止this引用在构造过程中逸出:
public class SafeListener {
private static final EventListener listener;
public SafeListener() {
listener = new EventListener() {
public void onEvent(Event e){
doSomeThing(e);
}
}
}
public static SafeListener newInstance(EventSource source){
SafeListener safe = new SafeListener();
source.registerListener(safe.listener);
return safe;
}
}
最新文章
- 使用curl进行https请求
- windows下CMake使用图文手册 Part 1
- C语言运算符优先级和口诀(转)
- 深入浅出C#中的静态与非静态
- CSS高级知识
- 递推DP URAL 1225 Flags
- 转:简单的Mysql主从复制设置
- Node入门教程(4)第三章:第一个 Nodejs 程序
- iOS 类方法 、野指针与空指针
- HTTPS加密那点事-对称、非对称加密、数字证书
- xml 转换成对象(采用反射机制对对对象属性赋值)
- 利用ENVI FX从RGB提取建筑物轮廓
- 【非专业前端】vue+element+webpack
- kafka集群的错误处理--kafka一个节点挂了,导致消费失败
- XPath简介及节点
- dict的items()方法于iteritems()方法的不同
- 【架构师之路】Nginx负载均衡与反向代理—《亿级流量网站架构核心技术》
- 结合领域驱动设计的SOA分布式软件架构
- mysql通过“延迟关联”进行limit分页查询优化的一个实例
- 静默安装oracle 11g,环境预检查时报错,SEVERE: [FATAL] PRVF-0002 : 无法检索本地节点名