zoukankan      html  css  js  c++  java
  • 安卓开发(Java)中关于final关键字与线程安全性

    前言

    学习新知识固然重要,但是时常往回看看,温故知新是很必要的。回顾一下线程安全性和final关键字。

    正文

    从Java 5开始,final keyword一个特殊用法是在并发库中一个非常重要且经常被忽视的武器。实质上,可以使用final来确保在构造对象时,访问该对象的另一个线程不会看到处于部分构造状态的对象,否则可能会发生这种情况。这是因为当作为对象变量的一个属性时,final作为其定义的一部分具有以下重要特征:

    当构造函数退出时,final keyword的值保证对访问构造对象的其他线程可见。

    使用final是所谓的安全发布(safe publication)的一种方式,这里,发布(publication)一个地相意味着在一个线程中创建它,同时另一个线程在之后的某时刻可以引用到该新创建的对象。当JVM调用对象的构造函数时,它必须将各成员赋值,同时存储一个指向该对象的指针。就像其他任何的数据写入一样,这可能是乱序的,and their application to main memory can be delayed and other processors can be delayed unless you take special steps to combat this(看不太懂,是不是说“把他们写回主存可能推迟,并且其他的处理器(看到变化)也会推迟,要客服这一点,除非采取非常步骤”)。特别的,指向对象的引用可能在成员变量提交之前(导致如此的原因之一是编译器的指令重排ordering:if you think about how you'd write things in a low-level language such as C or assembler, it's quite natural to store a pointer to a block of memory, and then advance the pointer as you're writing data to that block)就被写入到主存并被访问到了。这样会导致另一个线程看到了一个不合法或不完整的对象。

    而final可以防止此类事情的发生:如果某个成员是final的,JVM规范做出如下明确的保证:一旦对象引用对其他线程可见,则其final成员也必须正确的赋值了。

    final的对象引用

    对象的final成员成员的值在当退出构造函数时,他们也是最新的。这意味着:

    final类型的成员变量的值,包括那些用final引用指向的collections的对象,是读线程安全而无需使用synchronization的

    注意,如果你有一个指向collection,数组或其他可变对象的final引用,如果存在其他线程访问,仍然需要使用同步机制来访问该对象(或使用ConcurrentHashMap)。

    因此,不可变对象(指所有的成员都是final并且成员要么是基本类型,要么指向另一个不可变对象)可以并发访问而无需使用同步机制。通过final引用读取“实际不可变”对象(指成员虽然实际并不是final,然而却从不会改变)也是安全的。然而,从程序设计的角度来看,在此种情况下强化不可变性是明智的(如用Collections.unmodifiableList()封装一个collection)。That way, you'll spot bugs introduced when one of your colleagues naughtily attempts to modify a collection that you didn't intend to be modified!

    使用final的限制条件和局限性

    当声明一个final成员时,必须在构造函数退出前设置它的值,如下:

    public class MyClass {

        private final int myField = 3;

        public MyClass() { ... }

    }

    或者

    public class MyClass {

        private final int myField;

        public MyClass() {

            ...

            myField = 3;

            ...

        }

    }

    需要强调的是将指向对象的成员声明为final只能将该引用设为不可变的,而非所指的对象。例如如果一个list声明如下:

    private final List myList =new ArrayList();

    仍然可以修改该list

    myList.add("Hello");

    然而,声明为final可以保证如下操作不合法:

    myList =new ArrayList();

    myList= someOtherList;

    什么时候应该使用final

    一个答案就是“尽可能的使用”。任何你不希望改变的(基本类型,或者指向一个对象,不管该对象是否可变)一般来讲都应该声明为final。另一种看待此问题的方式是:

    如果一个对象将会在多个线程中访问并且你并没有将其成员声明为final,则必须提供其他方式保证线程安全

    “其他方式”可以包括声明成员为volatile,使用synchronized或者显式Lock控制所有该成员的访问。

    大家往往忽视的典型case是在一个线程创建一个对象,而后在另一个线程使用,如一个通过ThreadPoolExecutor的对象。这种情况下,必须保证该对象的线程安全性:这和线程的并发访问关系不大,主要是因为在其生命周期内,不同的线程会在任意时刻访问它(还是内存模型的问题吧)



    作者:AirrWang
    链接:https://www.jianshu.com/p/ba764ca54262
    來源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
  • 相关阅读:
    84. Largest Rectangle in Histogram (Solution 2)
    84. Largest Rectangle in Histogram (Solution 1)
    73. Set Matrix Zeroes
    【JavaScript】Symbol 静态方法
    【JavaScript】Date
    【JavaScript】Math
    725. Split Linked List in Parts把链表分成长度不超过1的若干部分
    791. Custom Sort String字符串保持字母一样,位置可以变
    508. Most Frequent Subtree Sum 最频繁的子树和
    762. Prime Number of Set Bits in Binary Representation二进制中有质数个1的数量
  • 原文地址:https://www.cnblogs.com/ldq2016/p/9650902.html
Copyright © 2011-2022 走看看