- 在单线程的环境下,如果向一个变量先写入一个值,然后在没有写干涉的情况下读取这个变量的值,那这个时候读取到的这个变量的值应该是之前写入的那个值。这本来是一个很正常的事情。但是在多线程环境下,读和写发生在不同的线程中的时候,可能会出现:读线程不能及时的读取到其他线程写入的最新的值。这就是所谓的可见性
-
1.引出问题
一个测试代码:
public static boolean stop = false;
public static void main(String[] args) throws InterruptedException {
// 创建线程
Thread thread = new Thread(() -> {
int i = 0;
// 判断静态变量是否为true,如果不是则一直执行
while (!stop) {
i++;
System.out.println("rs:" + i);
}
});
// 线程启动
thread.start();
// 睡眠一秒
Thread.sleep(1000);
// 变更静态变量
stop = true;
}
预计结果:
1.主线程执行,2.新线程启动,3.主线程睡眠一秒,4.一秒之后子线程while判断失效结束循环,线程结束。
测试结果:
while一直循环,程序无法停止。
- 尝试变更代码得到预计结果
1.添加一条打印语句
public static boolean stop = false;
public static void main(String[] args) throws InterruptedException {
// 创建线程
Thread thread = new Thread(() -> {
int i = 0;
// 判断静态变量是否为true,如果不是则一直执行
while (!stop) {
i++;
// 添加一条打印语句
System.out.println("rs:" + i);
}
});
// 线程启动
thread.start();
// 睡眠一秒
Thread.sleep(1000);
// 变更静态变量
stop = true;
}
执行结果:
程序正常结束。
2.让子线程睡眠
public static boolean stop = false;
public static void main(String[] args) throws InterruptedException {
// 创建线程
Thread thread = new Thread(() -> {
int i = 0;
// 判断静态变量是否为true,如果不是则一直执行
while (!stop) {
i++;
// 让子线程睡眠
try {
Thread.sleep(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 线程启动
thread.start();
// 睡眠一秒
Thread.sleep(1000);
// 变更静态变量
stop = true;
}
执行结果:
程序正常结束
3.对静态变量添加一个 volatile 属性
//添加volatile属性
public volatile static boolean stop = false;
public static void main(String[] args) throws InterruptedException {
// 创建线程
Thread thread = new Thread(() -> {
int i = 0;
// 判断静态变量是否为true,如果不是则一直执行
while (!stop) {
i++;
}
});
// 线程启动
thread.start();
// 睡眠一秒
Thread.sleep(1000);
// 变更静态变量
stop = true;
}
运行结果:
程序正常结束。
-
尝试
- print就可以导致循环结束
是因为当前安装的java虚拟机是server版本,server版本内部对代码做了一个JIT深度优化(活性失败)。
查询当前jvm版本,如下图:
while (!stop) {
i++;
}
//这段代码等价于
if (!stop) {
while (true) {
i++;
}
}
如果想要jvm不对代码进行优化可以在启动参数中添加:-Djava.compiler=NONE
原始代码测试运行:
程序正常结束。
查看 System.out.print(); 底层代码,发现调用了
1.IO流
2.synchronized
那我们分别对代码进行 IO操作,和 加锁操作(记得删掉启动参数中的-Djava.compiler=NONE)
public static boolean stop = false;
public static void main(String[] args) throws InterruptedException {
// 创建线程
Thread thread = new Thread(() -> {
int i = 0;
// 判断静态变量是否为true,如果不是则一直执行
while (!stop) {
i++;
synchronized (TestDemoDel.class){
}
}
});
// 线程启动
thread.start();
// 睡眠一秒
Thread.sleep(1000);
// 变更静态变量
stop = true;
}
public static boolean stop = false;
public static void main(String[] args) throws InterruptedException {
// 创建线程
Thread thread = new Thread(() -> {
int i = 0;
// 判断静态变量是否为true,如果不是则一直执行
while (!stop) {
i++;
new File("tea.txt") ;
}
});
// 线程启动
thread.start();
// 睡眠一秒
Thread.sleep(1000);
// 变更静态变量
stop = true;
}
启动,执行。发现也会结束循环,程序停止。
-
概念
通过对上述代码查看汇编指令,使用HSDIS工具,具体的使用方法自行百度。
可以看到,使用volatile关键字或各种方式使程序停止之后,多了一个Lock指令。
0x00000000037028f3: lock add dword ptr [rsp],0h ;*putstatic stop
0x0000000002b7ddab: push 0ffffffffc4834800h ;*putstatic stop; -
硬件层面
CPU/内存/IO设备
- CPU层面增加了高速缓存
- 操作系统,进程、线程、| CPU时间片来切换
- 编译器的优化 ,更合理的利用CPU的高速缓存.
看下图:
高速缓存分为 L1,L2,L3
- L1d 数据缓存
- L1i 指令缓存
- L2二级缓存
- L3三级缓存
离线程越近,速度越高。
当一个线程不存在某条内容数据时,会去内存中加载,加载之后,会读取到CPU的高速缓存中,下一次会先从高速缓存中加载,如果缓存存在,那就可以直接使用。大大加快了线程IO性能的开销,从而提升性能的以及CPU的利用率。
当使用 volatile 关键字时,会对缓存加锁。使其从内存中读取数据。所以会使其线程可见