可见性 有问题就是: 某些线程读到了过期的数据, 导致程序错误
原因
一. JIT 优化
比如 这个
@Override
public void run() {
while (!toCancel) {
//......没有对toCancel更新代码
}
}
会被JIT编译器优化为
@Override
public void run() {
if (!toCancel){
while (true){
//...
}
}
因为JIT编译器 ,默认代码是为单线程而写的, 他会觉得:
既然while里面不去改
toCancel
, 那toCancel
就是不变的 , 不变的东西在循环里面一遍遍取重复检查 就是浪费, 只要检查一遍就好了啦, 我给你改一改, 意思是完全一样的!
二.存储
1.每个cpu都有其寄存器
如果,2个线程在2个cpu上各自运行,变量被分配到
其寄存器, 而不是主内存存储,就读不到另一个线程的变量了
2.高速缓存子系统
即使,没有被分配到寄存器,
处理器对主内存不是直接访问,而是通过高速缓存子系统
, 一个cpu上对变量的更新,可能只到这个cpu的写缓冲器
, 没有到达这个cpu的高速缓存
, 更加没到 主内存
3.其他cpu没更新
即使,写入了这个cpu的高速缓存
cpu会把这个变量更新的通知给其他cpu,
但是其他cpu , 收到通知, 可能只是把 自己的这个变量加入 无效化队列
并没有 直接根据通知内容 更新其高速缓存 相应变量
保证可见性
在声明变量的时候 加个 volatile
1.JIT编译器 , 看到这个 就知道这个变量会被多个线程共享, 不会再认为就一个线程会访问
2.cpu 读
这个变量的时候, 会 刷新处理器缓存, 写
这个变量的时候, 会冲刷处理器缓存
刷新处理器缓存:
从其他处理器的高速缓存或者主内存中, 对本线程也用到的变量 全部进行缓存同步
冲刷处理器缓存:
使对处理器对共享变量的更新 全部 最终写入该处理器的高速缓存或者主内存, 而不是停留在其写缓冲区
, 让其他处理器可同步
加锁(内部锁,显示锁都行)
除了保证原子性外, 也能保证可见性,
因为
- 获取锁的时候 刷新了处理器缓存(本线程更到最新)
- 释放锁时 冲刷处理器缓存(推送本线程的更新到高速缓存)
Java 语言规范 对父子线程的可见性保证
// 在子线程thread启动前更新变量data的值
data = 1;// 语句① 父线程赋值
thread.start();//子线程在其后开始, 保证能看到
语句① 的改动, 在子线程里面可见
thread.join();
// 读取并打印变量data的值
System.out.println(data);
thread子线程结果以后, 它对data的修改, 其主线程 能看到
网友评论