对于 volatile 这个关键字, 相信很多朋友都听说过, 甚至使用过, 这个关键字虽然字面上理解起来比较简单, 但是要用好起来却不是一件容易的事.
这篇文章将从多个方面来讲解 volatile, 让你对它更加理解.
计算机中为什么会出现线程不安全的问题
volatile 既然是与线程安全有关的问题, 那我们先来了解一下计算机在处理数据的过程中为什么会出现线程不安全的问题.
大家都知道, 计算机在执行程序时, 每条指令都是在 CPU 中执行的, 而执行指令过程中会涉及到数据的读取和写入. 由于程序运行过程中的临时数据是存放在主存 (物理内存) 当中的, 这时就存在一个问题, 由于 CPU 执行速度很快, 而从内存读取数据和向内存写入数据的过程跟 CPU 执行指令的速度比起来要慢的多, 因此如果任何时候对数据的操作都要通过和内存的交互来进行, 会大大降低指令执行的速度.
为了处理这个问题, 在 CPU 里面就有了高速缓存 (Cache) 的概念. 当程序在运行过程中, 会将运算需要的数据从主存复制一份到 CPU 的高速缓存当中, 那么 CPU 进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据, 当运算结束之后, 再将高速缓存中的数据刷新到主存当中.
我举个简单的例子, 比如 cpu 在执行下面这段代码的时候,
t = t + 1;
会先从高速缓存中查看是否有 t 的值, 如果有, 则直接拿来使用, 如果没有, 则会从主存中读取, 读取之后会复制一份存放在高速缓存中方便下次使用. 之后 cup 进行对 t 加 1 操作, 然后把数据写入高速缓存, 最后会把高速缓存中的数据刷新到主存中.
这一过程在单线程运行是没有问题的, 但是在多线程中运行就会有问题了. 在多核 CPU 中, 每条线程可能运行于不同的 CPU 中, 因此每个线程运行时有自己的高速缓存(对单核 CPU 来说, 其实也会出现这种问题, 只不过是以线程调度的形式来分别执行的, 本次讲解以多核 cup 为主). 这时就会出现同一个变量在两个高速缓存中的值不一致问题了.
例如:
两个线程分别读取了 t 的值, 假设此时 t 的值为 0, 并且把 t 的值存到了各自的高速缓存中, 然后线程 1 对 t 进行了加 1 操作, 此时 t 的值为 1, 并且把 t 的值写回到主存中. 但是线程 2 中高速缓存的值还是 0, 进行加 1 操作之后, t 的值还是为 1, 然后再把 t 的值写回主存.
此时, 就出现了线程不安全问题了.
- int a = 1;
- int b = 2;
- public class NoVisibility{
- private static boolean ready;
- private static int number;
- private static class Reader extends Thread{
- public void run(){
- while(!ready){
- Thread.yield();
- }
- System.out.println(number);
- }
- }
- public static void main(String[] args){
- new Reader().start();
- number = 42;
- ready = true;
- }
- }
- public class Test{
- public static volatile int t = 0;
- public static void main(String[] args){
- Thread[] threads = new Thread[10];
- for(int i = 0; i <10; i++){
- // 每个线程对 t 进行 1000 次加 1 的操作
- threads[i] new Thread(new Runnable(){
- @Override
- public void run(){
- for(int j = 0; j < 1000; j++){
- t = t + 1;
- }
- }
- });
- threads[i].start();
- }
- // 等待所有累加线程都结束
- while(Thread.activeCount()> 1){
- Thread.yield();
- }
- // 打印 t 的值
- System.out.println(t);
- }
- }
来源: https://www.cnblogs.com/kubidemanong/p/9505944.html