java中的volatile关键字
前言
本文旨在讲述volatile的理解和简单实现,帮助大家了解并运用该字段。
概念及作用
volatile用来修饰不同线程访问和修改的变量,保证可见性和有序性(注意:该字段是在多个线程中使用的!)。
分析
我们从如下几个方面来分析这个字段:
1.多线程
(1)概念
进程:一个正在执行的程序,是系统对资源进行分配和调度的独立单位
线程:进程中的一个控制单元,一个进程至少含有一个线程
(2)使用
第一种继承Thread类,第二种实现Runnable接口,这两种方式大家应该都可以熟练使用,这里就不贴代码了
两种实现方式区别:
①继承Thread类只能后的类不能再去继承别的类,局限性大,而实现Runnable接口可以继承其他类,扩展性强
②实现Runnable接口方式创建线程可以共享同一个目标对象,实现了多个相同线程处理同一份资源
(3)线程的生命周期
Thread t = new Thread() —— 就绪状态
t.start()-run() —— 可执行状态
sleep()和wait() —— 阻塞状态
t.stop() —— 死亡状态
2.java内存模型
主内存:存储共享变量本身,所有线程共享
工作内存:线程各自的工作空间,存放变量的副本
读写操作:线程对共享变量的操作都在各自的工作内存中进行,变量的传递在主内存中进行(如A线程读取主内中的变量T到A工作内存修改后再写入主内存,B线程再读取主内存的值,这样共享变量通过主内存进行传递)
为什么java内存需要这么做?
CPU缓存的访问速度远远高于主内存,这里的工作内存就是缓存,这样做提升了程序的执行效率
3.volatile关键字
(1)可见性:某线程对volatile修饰的变量值修改后,其它线程获取到该变量的值绝对是最新值,如下场景:
class Test(){
private boolean isStop;
Thread t1 = new Thread();
Thread t2 = new Thread();
//修改值
t1.start(){
@Override
public void run(){
while(!isStop){
doIt();
}
}
};
//读取值
t2.start(){
@Override
public void run(){
isStop = true;
}
};
}
这段代码很典型,很多人会用这样的方式来中断线程,但是这段代码一定能中断线程1吗?
不一定,大多数时候可以,但发生了线程1就会进入死循环;因为当线程2修改了isStop值后还没来得及写入到主存中就去做其它的了,线程1不知道isStop的值被线程2修改,所以会继续循环。
解决方案:用volatile修饰isStop变量,即
private volatile boolean isStop;
第一:volatile强制将修改后的值立即写入主内存中
第二:使用volatile后,线程2修改isStop时,会导致线程1中的副本isStop缓存失效,然后线程1此时会等主内存中的isStop值更新后再去读取,这样就保证了读取的值时最新值
(2)有序性:
JVM的即时编译器中存在指令重排序优化,volatile禁止这种优化,保证程序的有序性,如下场景:
public class Test {
private static Test test = null;
private Test() {
}
public static Test getInstance() {
if (test == null) {
synchronized (Test.class) {
if (test == null) {
test = new Test();
}
}
}
return test;
}
}
这段代码是典型的double check懒汉式单例模式,那么一定会完全成功返回一个正确的test对象吗?
不一定,主要在于test = new Test(),这不是一个原子操作,在JVM中该句做了如下3中事情:
①给test分配内存
②调用Test构造函数来初始化成员变量
③将test对象指向分配的内存地址
但是JVM即时编译器会进行指令重排序优化,即①②③顺序变为①③②,当线程1执行到②时恰巧线程2要拿这个对象,此时判断test不为null,那么会返回一个没有初始化的错误test对象,所以需要加上volatile修饰,这样就会禁止指令重排序优化
private volatile static Test test = null;
结语
volatile字段的理解和简单实现就到这,希望对大家有用,如果有更好想法,欢迎留言。