• volatile特性


    volatile保证可见性

       一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

      1)保证了不同线程对这个变量进行操作的可见性,即一个线程修改了某个变量的值,这新值对其它线程来说是立即可见的

      2)禁止进行指令重排序

     volatile不能确保原子性

     在访问volatile变量时,不会执行加锁操作,因此也就不会执行线程阻塞。所以volatile变量是一种比sychhronized关键字更轻量级的同步机制

     关于这一点,可看个例子:

    public class VolatileDemo {
    
        private volatile int number = 0;
    
        public int getNumber() {
            return this.number;
        }
    
        public void add() {
            this.number++;
        }
    
        public static void main(String[] args) {
            final VolatileDemo demo = new VolatileDemo();
            for (int i = 0; i < 500; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        demo.add();
                    }
                }).start();
            }
    
            // 如果还有子线程在运行,主线程就让出CPU资源
            // 直到所有的子线程都运行完了,主线程再继续往下执行
            while (Thread.activeCount() > 1) {
                Thread.yield();
            }
            System.out.println("number:" + demo.getNumber());
        }
    
    }

     大家想一下这段程序的输出结果是多少?也许有些朋友认为是500,但是事实上运行它会发现偶尔会出现小于500的值。如果在number++前,加一个Thread.sleep(),效果更明显

     接下来,我们分析一下为啥会出现这种情况:

     假如某个时刻变量number=5

       1. 线程A读取number的值,然后阻塞

       2. 线程B读取number的值

       3. 线程B执行加1操作

       4. 线程B写入最新的number的值(6)到主内存中。此时线程A工作内存中number还是5

       5. 线程A执行加1操作

       6. 线程A写入最新的number值(6)到主内存中

     两个线程分别进行了一次自增操作后,number只增加了1。

     根源就在自增操作不是原子性操作,而volatile也无法保证对变量的任何操作都是原子性

     解决方案:可以通过synchronizedlock,进行加锁,来保证操作的原子性。也可以通过AtomicInteger

     具体例子:

        public void add2() {
            // 加上synchronized后,就可以保证原子性
            synchronized (this) {
                this.number++;
            }
        }
    
        public void add3() {
            // 加上lock后,保证原子性
            lock.lock();
            try {
                this.number++;
            } finally {
                lock.unlock();
            }
        }
    
        //AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减
        private AtomicInteger count = new AtomicInteger(0);
    
        public void add4() {
            count.getAndIncrement();
        }

     volatile的适用场景:

      1、对变量的写入操作不依赖其当前值(如 number++ 、count = count * 5等)

      2、该变量没有包含在具有其它变量的不变式中

           3、volatile的一个重要作用就是和CAS结合,保证了原子性,详细的可以参见java.util.concurrent.atomic包下的类,比如AtomicInteger

     参考资料:http://www.importnew.com/24082.html

      

  • 相关阅读:
    经典回溯问题--八皇后dfs递归回溯求解【DFS】
    CSP认证考试(第九次)第一题
    C++字符串和数字格式转化(使用sprintf()和sscanf()函数)
    2016蓝桥杯C++A组第六题 寒假作业【暴力搜索】
    先序非递归建立二叉树
    sqlsrv数据库复杂语句1
    tp5域名配置
    JavaScript使用 value 属性
    数据库随机查询6条数据
    文件目录问题
  • 原文地址:https://www.cnblogs.com/xuwenjin/p/9051179.html
Copyright © 2020-2023  润新知