欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java中的volatile关键字

程序员文章站 2024-03-23 12:10:34
...

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内存模型
java中的volatile关键字
主内存:存储共享变量本身,所有线程共享
工作内存:线程各自的工作空间,存放变量的副本
读写操作:线程对共享变量的操作都在各自的工作内存中进行,变量的传递在主内存中进行(如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字段的理解和简单实现就到这,希望对大家有用,如果有更好想法,欢迎留言。