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并发编程与实战》龙果学院

  • 相关阅读:
    作业3月30号
    21、,模块与包的使用
    作业3月26号
    20、面向函数与匿名函数及模块
    作业3月25号
    19、迭代器及函数的递归调用
    作业3月24号
    06-函数
    3.17---购物车练习
    3.15---文件处理练习2
  • 原文地址:https://www.cnblogs.com/pony1223/p/9375215.html
Copyright © 2011-2022 走看看