可见性
在电脑硬件中,CPU缓存要比内存的读取效率高很多,所以CPU会将内存中的数据读取到缓存中后进行操作。
每个线程都有自己的缓存,一个线程将内存中的数据读取到缓存中进行修改后,其他线程是感知不到的,这就出现了可见性问题
可见性问题代码逻辑如下:
public class test {
private static boolean flag = true;
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
while (flag) {
}
System.out.println("t1线程结束");
});
t1.start();
Thread.sleep(1000);
flag = false;
System.out.println("main线程结束");
}
}
线程t1将flag值读取到缓存中,发现flag为true,进入while循环。当主线程将flag值修改成false,并且写回主内存中后,因为线程t1的缓存中已经有了flag的值(虽然这个值是true,并不正确),所以不会去主内存中重新读取flag的值。主线程修改了flag的值,没有通知t1线程,t1线程自己也感知不到,这个就是可见性问题。
保证可见性
volatile关键字
volatile关键字修饰在成员变量上,线程对共享变量进行修改后,其他线程可以感知到
当修改一个volatile变量,JMM会将当前线程对应的缓存中的数据及时刷新到内存中,并且其他线程的缓存行标记为无效。其他线程会从主内存重新读取数据。
volatile修饰的数据操作编译成汇编指令后,会追加lock前缀,cpu执行时,会做几件事:
- 将当前缓存行修改后的数据立即写回主内存
- 其他CPU缓存中存储的此数据的缓存行标记为无效
public class test {
private volatile static boolean flag = true;
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
while (flag) {
}
System.out.println("t1线程结束");
});
t1.start();
Thread.sleep(1000);
flag = false;
System.out.println("main线程结束");
}
}
synchronized关键字
synchronized保证可见性的机制:
synchronized修饰的同步代码块或者同步方法,获取锁资源后,会将代码块中涉及的变量从缓存中移除,必须从内存中重新读取。并且释放锁资源后,会立即将缓存中的数据写到内存中
public class test {
private static boolean flag = true;
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
while (flag) {
synchronized (test.class) {
}
}
});
t1.start();
Thread.sleep(1000);
flag = false;
System.out.println("main线程结束");
}
}
lock锁
lock锁是基于volatile实现的。lock内部在加锁和释放锁时,会对volatile修饰的state变量进行加减操作
如果对volatile修饰的变量进行修改操作,cpu会执行lock前缀的指令。cpu在修改数据后,会立即同步到内存中,并且将其他变量值也同步到内存中。并且会将其他cpu缓存中的数据设置成无效,必须从内存中重新获取
lock与synchronized相同,也是加锁时将代码块中涉及的变量从缓存中移除,再从内存中读取
public class test {
private static boolean flag = true;
private static Lock lock = new ReentrantLock();
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
while (flag) {
lock.lock();
}
try {
} finally {
lock.unlock();
}
System.out.println("t1线程结束");
});
t1.start();
Thread.sleep(1000);
flag = false;
System.out.println("main线程结束");
}
}