• 线程同步机制——轻量级锁volatile


    volatile关键字可以作为轻量级锁,可用来实现轻量级线程同步机制。

    一、volatile的作用

    volatile关键字的作用包括:保障可见性、有序性。但不能保证原子性(volatile可以保证long/double型变量读写操作的原子性)。

    volatile变量具备两种特性

    第一是保证此变量对所有线程的可见性。

    第二个语义是禁止指令重排序优化。

    二、volatile变量怎么保证可见性?为什么在并发情况下无法保证原子性?

    为什么volatile不能保证原子性而Atomic可以? 

    三、使用volatile需要注意的地方

    1.volatile是否能保证数组中元素的可见性?

    当数组引用被声明为volatile时,只有引用而不是数组本身具有volatile语义。

    stackoverflow上也有相关的问题:java-volatile-arrayjava-volatile-array-my-test-results-do-not-match-the-expectations

    四、典型使用场景

    1.作为状态标志

    将 volatile 变量作为状态标志使用

    volatile boolean stop;   
      
    public void stop() {   
        stop = true;   
    }  
      
    public void doWork() {   
        while (!stop) {   
            // do something 
        }  
    }

    2.一次性安全发布(one-time safe publication)

    这种使用场景最被人熟知的就是单例了。可以参考这篇博客:单例模式 - singleton

    缺乏同步会导致无法实现可见性,这使得确定何时写入对象引用而不是原语值变得更加困难。在缺乏同步的情况下,可能会遇到某个对象引用的更新值(由另一个线程写入)和该对象状态的旧值同时存在。(这就是造成著名的双重检查锁定(double-checked-locking)问题的根源,其中对象引用在没有同步的情况下进行读操作,产生的问题是您可能会看到一个更新的引用,但是仍然会通过该引用看到不完全构造的对象)。

    实现安全发布对象的一种技术就是将对象引用定义为 volatile 类型。清单 3 展示了一个示例,其中后台线程在启动阶段从数据库加载一些数据。其他代码在能够利用这些数据时,在使用之前将检查这些数据是否曾经发布过。

    将 volatile 变量用于一次性安全发布

    public class BackgroundFloobleLoader {
        public volatile Flooble theFlooble;
     
        public void initInBackground() {
            // do lots of stuff
            theFlooble = new Flooble();  // this is the only write to theFlooble
        }
    }
     
    public class SomeOtherClass {
        public void doWork() {
            while (true) { 
                // do some stuff...
                // use the Flooble, but only if it is ready
                if (floobleLoader.theFlooble != null) 
                    doSomething(floobleLoader.theFlooble);
            }
        }
    }

    如果 theFlooble 引用不是 volatile 类型,doWork() 中的代码在解除对 theFlooble 的引用时,将会得到一个不完全构造的 Flooble。

    该模式的一个必要条件是:被发布的对象必须是线程安全的,或者是有效的不可变对象(有效不可变意味着对象的状态在发布之后永远不会被修改)。volatile 类型的引用可以确保对象的发布形式的可见性,但是如果对象的状态在发布后将发生更改,那么就需要额外的同步。

    3.独立观察(independent observation)

    安全使用 volatile 的另一种简单模式是:定期 “发布” 观察结果供程序内部使用。例如,假设有一种环境传感器能够感觉环境温度。一个后台线程可能会每隔几秒读取一次该传感器,并更新包含当前文档的 volatile 变量。然后,其他线程可以读取这个变量,从而随时能够看到最新的温度值。

    使用该模式的另一种应用程序就是收集程序的统计信息。清单 4 展示了身份验证机制如何记忆最近一次登录的用户的名字。将反复使用 lastUser 引用来发布值,以供程序的其他部分使用。

    将 volatile 变量用于多个独立观察结果的发布

    public class UserManager {
        public volatile String lastUser;
     
        public boolean authenticate(String user, String password) {
            boolean valid = passwordIsValid(user, password);
            if (valid) {
                User u = new User();
                activeUsers.add(u);
                lastUser = user;
            }
            return valid;
        }
    }

    4. “volatile bean” 模式

    volatile bean 模式适用于将 JavaBeans 作为“荣誉结构”使用的框架。在 volatile bean 模式中,JavaBean 被用作一组具有 getter 和/或 setter 方法 的独立属性的容器。volatile bean 模式的基本原理是:很多框架为易变数据的持有者(例如 HttpSession)提供了容器,但是放入这些容器中的对象必须是线程安全的。

    在 volatile bean 模式中,JavaBean 的所有数据成员都是 volatile 类型的,并且 getter 和 setter 方法必须非常普通 —— 除了获取或设置相应的属性外,不能包含任何逻辑。此外,对于对象引用的数据成员,引用的对象必须是有效不可变的。(这将禁止具有数组值的属性,因为当数组引用被声明为 volatile 时,只有引用而不是数组本身具有 volatile 语义)。对于任何 volatile 变量,不变式或约束都不能包含 JavaBean 属性。清单 5 中的示例展示了遵守 volatile bean 模式的 JavaBean:

    遵守 volatile bean 模式的 Person 对象

    @ThreadSafe
    public class Person {
        private volatile String firstName;
        private volatile String lastName;
        private volatile int age;
     
        public String getFirstName() { return firstName; }
        public String getLastName() { return lastName; }
        public int getAge() { return age; }
     
        public void setFirstName(String firstName) { 
            this.firstName = firstName;
        }
     
        public void setLastName(String lastName) { 
            this.lastName = lastName;
        }
     
        public void setAge(int age) { 
            this.age = age;
        }
    }

    5.开销较低的读-写锁策略

    volatile 的功能还不足以实现计数器。因为 ++x 实际上是三种操作(读、添加、存储)的简单组合,如果多个线程凑巧试图同时对 volatile 计数器执行增量操作,那么它的更新值有可能会丢失。

    然而,如果读操作远远超过写操作,则可以结合使用内部锁和 volatile 变量来减少公共代码路径的开销。清单 6 中显示的线程安全的计数器使用 synchronized 确保增量操作是原子的,并使用 volatile 保证当前结果的可见性。如果更新不频繁的话,该方法可实现更好的性能,因为读路径的开销仅仅涉及 volatile 读操作,这通常要优于一个无竞争的锁获取的开销。

    @ThreadSafe
    public class CheesyCounter {
        // Employs the cheap read-write lock trick
        // All mutative operations MUST be done with the 'this' lock held
        @GuardedBy("this") private volatile int value;
     
        //无需同步
        public int getValue() { return value; }
     
        public synchronized int increment() {
            return value++;
        }
    }

    之所以将这种技术称之为 “开销较低的读-写锁” 是因为您使用了不同的同步机制进行读写操作。因为本例中的写操作违反了使用 volatile 的第一个条件,因此不能使用 volatile 安全地实现计数器 —— 您必须使用锁。然而,您可以在读操作中使用 volatile 确保当前值的可见性,因此可以使用锁进行所有变化的操作,使用 volatile 进行只读操作。其中,锁一次只允许一个线程访问值,volatile 允许多个线程执行读操作,因此当使用 volatile 保证读代码路径时,要比使用锁执行全部代码路径获得更高的共享度 —— 就像读-写操作一样。然而,要随时牢记这种模式的弱点:如果超越了该模式的最基本应用,结合这两个竞争的同步机制将变得非常困难。

    总结:

    1.volatile的作用?

    2.volatile的典型应用场景?

    3.volatile能否保证数组中元素的可见性

    4.volatile是如何保证可见性?

    5.volatile与synchronized的区别?

    参考:正确使用 Volatile 变量

  • 相关阅读:
    Silverlight OA系统(Silverlight办公自动化系统)
    Silverlight 3把多点触摸带进Web世界
    风云的银光志Silverlight4.0教程之使用CompositeTransform复合变形特效实现倒影
    最近团队完成的Silverlight 在线棋牌网络游戏
    Silverlight 3 多点触摸功能在Windows 7 中的应用
    风云的银光志Silverlight4.0教程之遍历访问客户端用户的本地文件
    网上Silverlight项目收集
    彻底解决Windowless=true情况下TextBox不能输入的问题
    相约微软大厦,Silverlight4.0技术分享线下活动明天下午正式开始!
    Silverlight WebOS(Web操作系统)
  • 原文地址:https://www.cnblogs.com/rouqinglangzi/p/9036550.html
Copyright © 2020-2023  润新知