zoukankan      html  css  js  c++  java
  • Java并发编程原理与实战十二:深入理解volatile原理与使用

    volatile:称之为轻量级锁,被volatile修饰的变量,在线程之间是可见的。

    可见:一个线程修改了这个变量的值,在另一个线程中能够读取到这个修改后的值。

    synchronized除了线程之间互斥之外,还有一个非常大的作用,就是保证可见性。以下demo即保证a值的可见性。

    首先来看demo:

    复制代码
    package com.roocon.thread.t7;


    public class Demo {
    private int a = 1;

    public int getA() {
    return a;
    }

    public void setA(int a) {
    try {
    Thread.sleep(200);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    this.a = a;
    }

    public static void main(String[] args) {
    Demo demo = new Demo();
    new Thread(new Runnable() {
    @Override
    public void run() {
    demo.setA(10);
    }
    }).start();
    new Thread(new Runnable() {
    @Override
    public void run() {
    System.out.println(demo.getA());
    }
    }).start();

    try {
    Thread.sleep(500);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println("最终结果:" + demo.getA());
    }
    }
    复制代码

    运行结果:

    1
    最终结果:10

    解释:线程1执行set操作,但是,线程2可能在线程1执行set操作成功之前就进行了get操作,这样,get得到的仍然是修改之前的a值

    那么,如何保证线程1在执行set操作时,其他线程得到的a值是线程1修改后的值呢?采用同步锁可以实现效果。同步方法上锁的是同一个实例,因此,在执行set方法前,线程1获取了实例锁,那么,其他线程在执行get方法时,必须获得同一把实例锁才可以得到a的值,所以,必须等待线程1释放实例锁后,其他线程才可以继续执行同步的get方法。这样,就可以保证其他线程得到的a值一定是线程1修改后的值。

    复制代码
    package com.roocon.thread.t7;


    public class Demo {
    private int a = 1;

    public synchronized int getA() {
    return a;
    }

    public synchronized void setA(int a) {
    try {
    Thread.sleep(200);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    this.a = a;
    }

    public static void main(String[] args) {
    Demo demo = new Demo();
    new Thread(new Runnable() {
    @Override
    public void run() {
    demo.setA(10);
    }
    }).start();
    new Thread(new Runnable() {
    @Override
    public void run() {
    System.out.println(demo.getA());
    }
    }).start();

    try {
    Thread.sleep(500);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println("最终结果:" + demo.getA());
    }
    }
    复制代码

    运行结果:

    10
    最终结果:10

    当然,要清楚的一点是,这里的输出统一仅仅是基于线程1的set操作是先于线程2的get操作执行的。但是,要知道,两个线程并发执行,不一定是set操作一定优先get操作执行的。

    同样的,volatile也可以保证可见性。因为synchronized是重量级锁,所以,使用volatile会更好。

    复制代码
    package com.roocon.thread.t7;
    
    
    public class Demo {
        private volatile int a = 1;
    
        public int getA() {
            return a;
        }
    
        public void setA(int a) {
            this.a = a;
        }
    
        public static void main(String[] args) {
            Demo demo = new Demo();
            new Thread(new Runnable() {
                @Override
                public void run() {
                   demo.setA(10);
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(demo.getA());
                }
            }).start();
    
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("最终结果:" + demo.getA());
        }
    }
    复制代码

    运行结果:

    10
    最终结果:10

    再来看个demo理解volatile的可见性:

    复制代码
    package com.roocon.thread.t7;
    
    public class Demo2 {
        public volatile boolean run = false;
    
        public static void main(String[] args) {
            Demo2 demo2 = new Demo2();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 1; i< 5; i++) {
                        System.out.println("执行了第" + i +"次");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    demo2.run = true;
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while(!demo2.run){
    
                    }
                    System.out.println("线程2执行了");
                }
            }).start();
        }
    }
    复制代码

    运行结果:

    执行了第1次
    执行了第2次
    执行了第3次
    执行了第4次
    线程2执行了

    这里需要注意的是,volatile只能保证线程的可见性,但是并不能保证原子性操作。如果volatile修饰的变量涉及到非原子操作,那么,我们需要使用synchronized来保证它的安全性。

    复制代码
    package com.roocon.thread.t7;
    
    
    public class Demo {
        private volatile int a = 1;
    
        public synchronized int getA() {
            return a++;
        }
    
        public synchronized void setA(int a) {
            this.a = a;
        }
    
        public static void main(String[] args) {
            Demo demo = new Demo();
            new Thread(new Runnable() {
                @Override
                public void run() {
                   demo.setA(10);
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(demo.getA());
                }
            }).start();
    
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("最终结果:" + demo.getA());
        }
    }
    复制代码

    运行结果:

    10
    最终结果:11

    volatile:

    在多处理器的系统上,它会执行一下步骤:

    1.将当前处理器缓存行的内容写回到系统内存

    2.这个写回到内存的操作会使得在其他CPU里缓存了该内存地址的数据失效

    3.其他CPU缓存数据失效,则会重新去内存中读取值,也就是被修改的数据

    这里要理解的一个概念是缓存行,缓存行是CPU缓存的一个基本单位。

    硬盘---内存--CPU缓存,内存的读取速度比硬盘快,CPU缓存的读取速度比内存更高效。

    volatile和synchronized的比较:

    synchronized是完全可以替换volatile的,只是volatile相对synchronized是轻量级锁。

    volatile是不可以完全替换synchronized的,因为volatile只能保证可见性,并不能保证操作的原子性。所以,很多情况下,两者会相互结合使用。

    参考资料:

    《java并发编程与实战》龙果学院

  • 相关阅读:
    Java实现 LeetCode 400 第N个数字
    Java实现 LeetCode 400 第N个数字
    Java实现 LeetCode 399 除法求值
    Java实现 LeetCode 399 除法求值
    Java实现 LeetCode 399 除法求值
    Java实现 LeetCode 398 随机数索引
    Java实现 LeetCode 398 随机数索引
    Java实现 LeetCode 398 随机数索引
    linux中的cd ..和cd -命令有什么区别?
    GCC使用
  • 原文地址:https://www.cnblogs.com/pony1223/p/9375215.html
Copyright © 2011-2022 走看看