持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第10天,点击查看活动详情
基本介绍
可见性是值,一个线程对共享变量修改另一个线程可以看到最新的结果
代码示例
@Slf4j(topic = "c.Test32")
public class Test32 {
static Boolean stop = false;
public static void main(String[] args) {
new Thread(()->{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
stop = true;
log.debug("把stop修改为true");
}).start();
foo();
}
static void foo(){
int i=0;
while (!stop){
i++;
}
log.debug("stopped...c:{}",i);
}
}
结果:
程序没有结束:
18:44:55.991 c.Test32 [Thread-0] - 把stop修改为true
解释:
上述代码表达的意思是,我们创建了一个stop成员变量默认为false 我们创建一个方法foo 它里面有一个while循环 只有stop为true时 它才能跳出循环,然后 我们创建了一个线程 我们在它里面使stop变为true,我们使线程在0.1s后再执行stop=true,所以理论上来说 我们在0.1s后 while循环就应该退出并打印循环次数了,可是我们却发现 这个循环并没有退出 也就是,对于while循环来说此时stop还是false
对于上述例子的解释
解释:还是上面那张图,不过需要加一个条件 那就是每个副本在进行写操作后都会更新主存中的数据 更新后其它线程的副本也需要更新为主存中的数据 达到数据同步,但是其中会有一个问题就是 上述代码的问题 如果一个线程中有一个一直执行的代码块 就叫做热点代码块 jvm的jit就会对其进行优化 以后这个代码块中的不进行写操作的变量 就会直接从副本中拿 就算有其他线程更新了主存中的数据 这个线程对于这个变量依旧从副本中拿。 对于例子来说:由于t0多次读取while(!stop)导致jvm认为while这部分代码块是热点代码 然后jvm里jit编译器就做了优化 因为stop一直只有读操作 所以以后再读取直接读取高速缓存里的stop,但是其他代码的stop还用的还是最新主存拷贝的副本,所以才会出现,有一个线程更新了stop=true但是主线程就是获取不到 但是其他线程都可以获取到。 需要注意的一点是:只有处于热点代码块中的读操作的变量 才会一直从工作内存中获取,但是有写操作的变量 每次会更新到工作内存后还会更新到主存中,大家可以试试创建一个静态变量cnt=0 在while循环中cnt++,然后其他线程在睡眠几秒后持续读取cnt 会发现cnt一直在增加,也就说明cnt确实更新到主存中了
synchronized的可见性
@Slf4j(topic = "c.Test32")
public class Test32 {
static Boolean stop = false;
static String lock = "";
public static void main(String[] args) {
new Thread(()->{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
stop = true;
log.debug("把stop修改为true");
}).start();
new Thread(()->{
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("{}",stop);
}).start();
foo();
}
static void foo(){
int i=0;
while (true){
synchronized (lock){
if (stop)
break;
}
i++;
}
log.debug("stopped...c:{}",i);
}
}
结果:
19:47:45.452 c.Test32 [main] - stopped…c:5667729
19:47:45.452 c.Test32 [Thread-0] - 把stop修改为true
19:47:45.546 c.Test32 [Thread-1] - true
解释
可以看出我们利用synchronized也可以实现stop=true。
原因是:在Java内存模型中,synchronized规定,线程在加锁时, 先清空工作内存→在主存中拷贝最新变量的副本到工作内存 →执行完代码→将更改后的共享变量的值刷新到主内存中→释放互斥锁。