volatile关键字与内存可见性
为什么要利用多线程?
其实就是为了提高效率,尽可能去利用系统/CPU的资源。
但使用不当可能会造成性能更低。
因为涉及到 线程调度,上下文切换问题,线程创建销毁等问题,所以在用多线程时有许多注意事项。
主要关注 JUC包
volatile 关键字
来看下面一段代码
public class TestVolatile {
public static void main(String[] args)
{
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while (true)
{
if(td.isFlag()){
System.out.println("----------------");
break;
}
}
}
}
class ThreadDemo implements Runnable{
private boolean flag = false;
@Override
public void run() {
try {
//让该线程慢于主方法线程执行
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag =true;
System.out.println("flag="+isFlag());
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
代码逻辑:
- 线程对象
td
改变其中的flag
变量为true
- main方法(也是个线程)判断如果
td
对象的flag
属性为true
,则跳出循环
实际上运行结果:
main方法一直没有退出,一直在while
循环中
发生了什么?
线程 td
把 flag
变量为true
了,而main方法去读flag
依然是false
内存可见性
下面介绍一下JVM的内存模型
Java内存模型
-
共享内存的并发模型,线程间通过读-写共享变量(堆内存中的实例域,静态域和数组元素)来完成隐式通信
-
计算机高速缓存和缓存一致性
- CPU(高速)——缓存——内存(低速),有可能有多个处理器(核),会导致缓存不一致,处理器访问回缓存时需要遵循一些协议
-
JVM主内存与工作内存
- 线程A先把本地内存A更新的共享变量刷新到主内存中
- 线程B到主内存中读线程A之前已更新过的共享变量
- 这样线程AB就完成了通信
-
也就是说,main方法刚开始从主内存拿到的
flag
是false
,但后面td
线程将flag
改成了true
-
但此时main方法依然处在
while
循环中,由于while
循环执行效率很高,所以没办法在两次循环之间抽出时间从主内存中再次读取数据
总的来说,当多个线程操作共享数据时,彼此不可见,这就是 内存可见性问题
可以用同步锁来解决,在while
循环体里加上synchronized
关键字,对线程进行加锁,但效率很低,线程很多时,会造成很严重的阻塞等待
怎么解决?
使用volatile
关键字
- 当多个线程进行操作共享数据时,可以保证内存中的数据是可见的
- 即如果加了
volatile
关键字,任何线程都要从主内存中读取最新的数据 - 性能影响:稍微下降,禁止了指令重排序
上面的代码改动如下:
public class TestVolatile {
public static void main(String[] args)
{
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while (true)
{
if(td.isFlag()){
System.out.println("----------------");
break;
}
}
}
}
class ThreadDemo implements Runnable{
//加上了volatile关键字修饰
private volatile boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag =true;
System.out.println("flag="+isFlag());
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
发现运行后,main方法成功退出
其他问题
volatile
仅仅是一种较为轻量级的同步策略volatile
不具备**“互斥性”**volatile
无法保证变量的原子性