1. 前言
上次【保证原子性的几种方式,你都知道吗???】 经过一顿
大杂烩
后,列举了几种原子性的解决方案。这次我们继续上次的话题,我们来说说可见性
的解决方案。废话不多说,让我们赶快开始吧。
2. 什么是可见性。
让我们用一张图带你了解下什么是可见性。
可见性问题是基于CPU位置出现的,CPU处理速度非常快,相对CPU来说,去主内存获取数据这个事情太慢了,CPU就提供了L1,L2,L3的三级缓存,每次去主内存拿完数据后,就会存储到CPU的三级缓存,每次去三级缓存拿数据,效率肯定会提升。
这就带来了问题,现在CPU都是多核,每个线程的工作内存(CPU三级缓存)都是独立的,会告知每个线程中做修改时,只改自己的工作内存,没有及时的同步到主内存,导致数据不一致问题。
3. 哪几种方式解决可见性呢???
3.1 volatile
- volatile核心语义:
- volatile属性被写:当写一个volatile变量,JMM会将当前线程对应的CPU缓存及时的刷新到主内存中
- volatile属性被读:当读一个volatile变量,JMM会将对应的CPU缓存中的内存设置为无效,必须去主内存中重新读取共享变量
总之就是一句话:
读的时候直接到主内存中获取值。写的时候将CPU缓存中的数据强制同步到主内存中去
public class T08_Thread_Volatile {
private static volatile boolean flag = true;
public static void loop() {
while (flag) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(T08_Thread_Volatile::loop);
t1.start();
Thread.sleep(2000);
flag = false;
}
}
3.2 synchronized
synchronized
也是可以解决可见性问题的
如果涉及到了synchronized的同步代码块或者是同步方法,获取锁资源之后,将内部涉及到的变量从CPU缓存中移除,必须去主内存中重新拿数据,而且在释放锁之后,会立即将CPU缓存中的数据同步到主内存
public class T09_Thread_synchronized {
private static boolean flag = true;
public static void loop() {
while (flag) {
synchronized (T09_Thread_synchronized.class) {
}
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(T09_Thread_synchronized::loop);
t1.start();
Thread.sleep(2000);
flag = false;
}
}
3.3 lock锁
Lock锁保证可见性的方式和synchronized完全不同,synchronized基于他的内存语义,在获取锁和释放锁时,对CPU缓存做一个同步到主内存的操作
Lock锁是基于volatile实现的。Lock锁内部再进行加锁和释放锁时,会对一个由volatile修饰的state属性进行加减操作
如果对volatile修饰的属性进行写操作,CPU会执行带有lock前缀的指令,CPU会将修改的数据,从CPU缓存立即同步到主内存,同时也会将其他的属性也立即同步到主内存中。还会将其他CPU缓存行中的这个数据设置为无效,必须重新从主内存中拉取(缓存一致性协议)
public class T10_Thread_Lock {
private static boolean flag = true;
private static Lock lock = new ReentrantLock();
public static void loop() {
while (flag) {
try {
lock.lock();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(T10_Thread_Lock::loop);
t1.start();
Thread.sleep(1000);
flag = false;
}
}
3.3.1 源码查找如下:
4. 结论
以上就是
可见性
的实现方案。第一种跟最后一种原则上大致相同。但是第二种不太一样。好了,今天的分享就到这里了。如果大家觉得还可以的话,记得点赞收藏哦.