作用
关键字volatile的主要作用是使变量在多个线程间可见
举个例子:
class RunThread extends Thread {
private boolean isRunning = true;
public boolean isRunning() {
return isRunning;
}
public void setRunning(boolean running) {
isRunning = running;
}
@Override
public void run() {
System.out.println("进入run了");
while (isRunning == true) {
}
System.out.println("线程被停止了");
}
}
public class Demo3 {
public static void main(String[] args) {
RunThread runThread = new RunThread();
runThread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
runThread.setRunning(false);
System.out.println("已经赋值为false");
}
}
上述代码执行结果中,线程被停止这句话一直没有出现。
而给变量加上关键字volatile后:
class RunThread extends Thread {
volatile private boolean isRunning = true;
public boolean isRunning() {
return isRunning;
}
public void setRunning(boolean running) {
isRunning = running;
}
@Override
public void run() {
System.out.println("进入run了");
while (isRunning == true) {
}
System.out.println("线程被停止了");
}
}
public class Demo3 {
public static void main(String[] args) {
RunThread runThread = new RunThread();
runThread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
runThread.setRunning(false);
System.out.println("已经赋值为false");
}
}
则结果就明显不一样了,原因就是在runThread启动时,变量private boolean isRunning = true存在于公共堆栈及线程的私有堆栈中,在JVM被设置为-server模式时,为了线程运行的效率,线程一直在私有堆栈中取得isRunning的值是true,而代码thread.setRunning(false);虽然执行,更新的是公共堆栈中的isRunning变量值false,所以一直就是死循环状态,volatile关键字的作用就是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量值。
这个是线程私有堆栈模型:
线程公共内存模型:
缺点
volatile关键字最致命的缺点是不支持原子性。
比较synchronized和volatile:
1.volatile是线程同步的轻量级实现,他只能修饰变量,而synchronized可以修饰方法以及代码块。
2.多线程访问volatile不会发生阻塞,而synchronized会。
3.volatile可以保证数据的可见性,但不能保证原子性,而synchronized可以保证原子性,间接也可以保证可见性,因为他会将私有内存和公共内存中的数据做同步。
4.volatile解决的是变量在多个线程之间的可见性,而synchronized关键字解决的是多个线程之间访问资源的同步性。
线程安全包含原子性和可见性两方面,Java同步机制都是围绕这两个方面来确保线程的安全的。
同时还需要注意一个点,就是在对有volatile关键字修饰的变量做一些操作时,比如对他进行加加操作,例如i++,这操作并不是一个原子操作,它分为:
1.从内存中取出i的值
2.计算i的值
3.将i写入内存中
假设计算第二步时,另一个线程也修改i的值,那么这个时候就会出现脏数据,解决办法就是加上synchronized关键字,所以volatile这个关键字本身并不处理原子性。
下面用图演示一下volatile关键字出现非线程安全原因。
1.read,load阶段:从主存赋值变量到当前线程工作内存
2.use,assign阶段:执行代码,改变共享变量值
3.store,write阶段:用工作内存数据刷新主存对应变量的值
对于volatile修饰的变量,JVM虚拟机只是保证从主存加载到线程工作内存的值是最新的,也就是volatile关键字解决的是变量读时的可见性问题,但无法保证原子性,对于多个线程访问同一个实例变量还是需要加锁同步。