• 关于java中final关键字与线程安全性


    在Java5中,final关键字是非常重要而事实上却经常被忽视其作为同步的作用。本质上讲,final能够做出如下保证:当你创建一个对象时,使用final关键字能够使得另一个线程不会访问到处于“部分创建”的对象,否则是会可能发生的。这是 因为,当用作对象的一个属性时,final有着如下的语义:

     当构造函数结束时,final类型的值是被保证其他线程访问该对象时,它们的值是可见的

    为什么是必须的

    使用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的对象。这种情况下,必须保证该对象的线程安全性:这和线程的并发访问关系不大,主要是因为在其生命周期内,不同的线程会在任意时刻访问它(还是内存模型的问题吧)

  • 相关阅读:
    繁体解决方案一,ASP,JSP,PHP,DotNet任何开发通用
    18.观察者模式(Observer Pattern)
    存储过程小技巧(2)从动态查询SQL中返回值给变量
    DotNet学习
    询问:有没有用C#写的C/S模式下的网页编辑工具
    dotNet繁体解决方案
    svn感叹号大全
    svn中出现红色感叹号
    Varnish配置,Error 503解决之道
    varnish清除缓存的方法
  • 原文地址:https://www.cnblogs.com/jxldjsn/p/6115764.html
Copyright © 2020-2023  润新知