本文共 2373 字,大约阅读时间需要 7 分钟。
想必大家都听说过volatile这个修饰词,可能在你对他的理解仅停留在保证数据准确性的层面。
今天详细说下volatile的一个功能和原理。说道volatile 千万不要和synchronize作用混淆了,都知道synchronize锁,主要是为了保证程序的执行的原子性,保证线程安全。
多线程三大特性:原子性、有序性、可见性。而volatile 就可以保障有序性和可见性,但是记住,它是不能保证原子性的。那么就先说说什么是原子性,什么是有序性,什么是可见性?
那么就来详细说下这三大特性:
这里就不演示了,不细说了,知道线程锁的基本上都知道这玩意,重点说说下面两个特性。
有序性? 保证程序的执行顺序? 我们写代码的时候不就是按照上下写的吗?
其实这里需要嵌入一个知识: 对象的创建过程对象的创建过程:
比如我们写了一行代码: ** class {int m = 8;} T t = new T();** 都知道程序在运行的java编译器会将代码编译成汇编码。而我们上述写的一行代码,被编译之后却是很多行 :介绍下:
new的时候,申请一个内存空间,默认成员变量是0, 同时在栈中产生一个 t 的对象。 然后:调用类的构造方法,这个时候才将复制8复制给你的m,此时你的内存空间才有了完整的对象空间数据。最后:将栈中的t 指向你内存对象,最终返回,才完成一个对象的new。
过程: 知道上面的知识之后, 现在知道你的一行代码并不是一行代码了吧。那么话说回来,为什么编译的时候好好端端的要给指令重排序的呢,难道先完成引用,再给m复制?确实可能是这样的,那就来说说为什么要给指令重排序呢?
其实很好理解,正规的说就是在不破坏和改变最终执行结果的情况下,保证程序效率对上下毫无关联的指令进行编译和运行时重排序,并行执行,最终达到提高程序效率。
是不是这就明白了就是字面上意思,可以看得见。这个针对于多线程来说的。
现在再说说线程是怎么调用的程序(知识点),可以结合着这个来看下现在还是简单说下:
一个线程需要执行的程序的时候,会向内存申请空间,然后形成自己的一个工作区,主要有什么指令+数据啥的。 那么多个线程执行的时候,每个线程都会有自己的一个工作区,而操作的数据是自己工作区的工作区内存,而并不能知道其他线程的数据。 现在有个共享资源需要多个线程进行同步的修改。上图: 这个时候线程需要现从主内存拿出数据count,修改完之后再写入主内存中,线程2去拿count在写入,这样的一个过程。 但是这样的一个过程存在一个问题,如果线程1写回主内存后,线程2没有及时拿到最新的数据,这样就会产生数据不一致,也就是不可见性问题。那么为什么会出现没有读取到最新数据的情况呢?
这就要了解一个知识点CPU缓存和缓存一致性问题(另出博客讲解),简单说下,cpu读取主内存数据时,考虑到效率问题,因为cpu的处理速度是内存的很多倍,所以未了提高效率,cpu会数据存到缓存中,防止下次继续访问,就可以直接用。如果缓存没有更新就回产生缓存一致性问题。那么volatile修饰之后,就会告诉其他cpu,现在缓存中的数据是无效的,要想获取数据直接去内存拿。
上代码:
public static void main(String[] args) throws InterruptedException { // 线程 1 new Thread( new Runnable() { @Override public void run() { // 如果flag为false的时候就结束,否则就一直运行着 while (flag){ System.out.println( "我是线程1,我在执行中" ); } } } ).start(); // 线程 2 new Thread( new Runnable() { @Override public void run() { // 将flag改成false,加一个synchronize保证输出和复制是原子操作 synchronized (this){ System.out.println( "我是线程2 我修改成功啦"); flag = false; } } } ).start(); }可以看到,线程2是用来结束线程1的,但是发现结束之后,线程1还是执行一条,这就是当时线程不可见情况。 加上volatile后:
转载地址:http://qjhof.baihongyu.com/