博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
多线程透析--volatile作用和原理|线程三大特性
阅读量:2048 次
发布时间:2019-04-28

本文共 2373 字,大约阅读时间需要 7 分钟。

多线程透析–volatile作用和原理|线程三大特性

想必大家都听说过volatile这个修饰词,可能在你对他的理解仅停留在保证数据准确性的层面。

今天详细说下volatile的一个功能和原理。

说道volatile 千万不要和synchronize作用混淆了,都知道synchronize锁,主要是为了保证程序的执行的原子性,保证线程安全。

多线程三大特性:原子性、有序性、可见性。而volatile 就可以保障有序性和可见性,但是记住,它是不能保证原子性的。

那么就先说说什么是原子性,什么是有序性,什么是可见性?

  • 1.原子性:一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。(简单说a,b两个指令程序,要么a,b都能执行结束,要么都不执行)
  • 2.有序性:就是程序的执行顺序,防止指令的重排序。(一会详细说,不要急,这里很多人不理解)
  • 3.可见性:线程执行对资源的变动,能立刻让其他线程知道。(一会细说)

那么就来详细说下这三大特性:

1.原子性(volalite不能保证)

这里就不演示了,不细说了,知道线程锁的基本上都知道这玩意,重点说说下面两个特性。

2.有序性

有序性? 保证程序的执行顺序? 我们写代码的时候不就是按照上下写的吗?

其实这里需要嵌入一个知识: 对象的创建过程

对象的创建过程:

比如我们写了一行代码: ** class {int m = 8;} T t = new T();**
都知道程序在运行的java编译器会将代码编译成汇编码。而我们上述写的一行代码,被编译之后却是很多行 :
在这里插入图片描述

介绍下:

在这里插入图片描述
new的时候,申请一个内存空间,默认成员变量是0,
同时在栈中产生一个 t 的对象。
在这里插入图片描述
然后:调用类的构造方法,这个时候才将复制8复制给你的m,此时你的内存空间才有了完整的对象空间数据。
在这里插入图片描述

最后:将栈中的t 指向你内存对象,最终返回,才完成一个对象的new。

过程:
在这里插入图片描述
知道上面的知识之后, 现在知道你的一行代码并不是一行代码了吧。那么话说回来,为什么编译的时候好好端端的要给指令重排序的呢,难道先完成引用,再给m复制?

确实可能是这样的,那就来说说为什么要给指令重排序呢?

其实很好理解,正规的说就是在不破坏和改变最终执行结果的情况下,保证程序效率对上下毫无关联的指令进行编译和运行时重排序,并行执行,最终达到提高程序效率。

是不是这就明白了

代码演示: 典型的案例就是DCL(Double Check Lock),单例模型中双重锁。(暂时不演示,后面补上)

3.可见性

就是字面上意思,可以看得见。这个针对于多线程来说的。

现在再说说线程是怎么调用的程序(知识点),可以结合着这个来看下

现在还是简单说下:

一个线程需要执行的程序的时候,会向内存申请空间,然后形成自己的一个工作区,主要有什么指令+数据啥的。
那么多个线程执行的时候,每个线程都会有自己的一个工作区,而操作的数据是自己工作区的工作区内存,而并不能知道其他线程的数据。
现在有个共享资源需要多个线程进行同步的修改。上图:
在这里插入图片描述
这个时候线程需要现从主内存拿出数据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/

你可能感兴趣的文章
(PAT 1118) Birds in Forest (并查集)
查看>>
数据结构 拓扑排序
查看>>
(PAT 1040) Longest Symmetric String (DP-最长回文子串)
查看>>
(PAT 1145) Hashing - Average Search Time (哈希表冲突处理)
查看>>
(1129) Recommendation System 排序
查看>>
PAT1090 Highest Price in Supply Chain 树DFS
查看>>
(PAT 1096) Consecutive Factors (质因子分解)
查看>>
(PAT 1019) General Palindromic Number (进制转换)
查看>>
(PAT 1073) Scientific Notation (字符串模拟题)
查看>>
(PAT 1080) Graduate Admission (排序)
查看>>
Tree UVA - 548 (DFS+建立二叉树)
查看>>
Play on Words UVA - 10129 (欧拉路径)
查看>>
mininet+floodlight搭建sdn环境并创建简答topo
查看>>
(计蒜客) 取石子游戏 (gcd算法灵活运用)
查看>>
Prime Path POJ - 3126 (BFS,素数距离)
查看>>
Wireless Network POJ - 2236 (并查集)
查看>>
【javascript】手写bind函数
查看>>
Kube-state-metrics的collectors配置
查看>>
使用Vmware装虚拟机Ubuntu
查看>>
【java】异常处理及捕获的理解
查看>>