欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

final关键词在多线程环境中的使用

程序员文章站 2022-07-12 11:54:43
...
  •  Contents
  • 为什么final在多线程中是必要的
  • final对象的引用
  • final的局限性
  • 什么时候需要使用final
自java 5 起, final关键词在并发中的一个特殊应用是非常重要而且常常被忽视的,实际上,fianl 可以保证正在创建中的对象不能被其他线程访问到,反之,不适用final的对象,是可以在创建的过程中被访问到的。因为,当一个对象的变量被用作属性的时候,final有以下重要的特征:
 
当构造函数退出时,final域的值是被保证对其他线程可见的。
 
  • 为什么这是必要的?
 
final域是一种被叫做“安全发布”的方式。对象的发布是指-在一个线程中创建一个对象,这个最新被创建的对象在未来的某个时刻被另外一个线程引用。当JVM执行对象的构造方法时,它必须将值存储到对象的各个域,并存储一个对象的指针。在其他任何情况下的数据写入(这种写入可能是无序的),访问主内存的应用程序可能被延迟,其他处理器的应用程序也可能被延迟,除非使用特殊的步骤确保这种延迟不发生。(这里是指对共享对象并发数据的写入存在竞态条件,事实上这种共享对象的并发写入,最终结果取决于最线程的执行顺序)尤其是,对象数据的指针可能存储在主存中,然后在对象的域提交之前被访问(这件事发生部分是因为编译指令如果你熟悉用低级语言,比如C或者汇编,来编写程序,很自然你会为一块内存分配一个指针,当你想向这块内存中写数据的时候就通过指针来操)。这种情况下会使得其他的线程可以访问到一个无效的或者是部分被创建的对象。
 
 
 
 
声明为final 可以防止这种情况(其它线程访问到部分对象)的发生:
 
final域:final域可以有效的避免竞态条件的发生是JVM规范的一部分,一旦对象的指针对其他线程是可见的,这个对象的final域的正确的值对其他对象也是可见的
 
 
  • final对象引用
 
通过final引用访问的任何对象的域 也可以保证 是和构造函数退出时一样是最新的:
 
final域的值,包括collection中引用的final对象,不需要使用synchronized在读取数据时可以保证是线程安全的
 
注意:如果有一个对collection的final引用,array 或者其他可变的对象,在多线程访问的情况下仍然需要同步所有的访问,(或者可以使用线程安全的类,如CurrentHashMap)。
 
因此,对于不可变对象(所有域都是final 并且创建后没有被修改,或者指向一个不可变对象)都可以被并发访问而不需要同步。
 
通过final引用对于那些“实际上不变”的对象的读取操作也是安全的,(实际上不变的对象是指域不是final的,但是创建之后没有被改变过)。但是从程序设计的角度来讲,最好声明为final的。
 
  • final的局限性
 
final域在声明的时候,这个域必须在构造函数退出之前被初始化完成。有两种方式可以初始化final域:
 
(1)
 
public class MyClass {
  private final int myField = 3;
  public MyClass() {
    ...
  }
}
 
(2)
public class MyClass {
  private final int myField;
  public MyClass() {
    ...
    myField = 3;
    ...
  }
}
 
必须强调的一点是,当声明一个final的引用时, 只能说明该引用是不可变的,但是数据是可变的。比如声明一个final 的list:
private final List myList = new ArrayList();
 
你仍然可以改变这个list:
myList.add("Hello");
 
然而下面的操作是不允许的:
myList = new ArrayList();
myList = someOtherList;
 
 
  • 什么时候需要使用final
一种说法是:任何不希望域被改变的时候都可以使用final
另外一种是:如果一个对象可以被多个线程访问到,但是并没有被声明为final,那么你需要提供额外的线程安全机制。
 
其他的方式包括声明为volatile 、使用synchronized、显示锁等
 
一种典型的应用是:在一个线程中创建一个对象,然后对象呗另一个线程使用,比如通过  ThreadPoolExecutor, 在这种情况下,必须要确保对象是线程安全的,这无关访问是不是并发的,而是关于对象在生命周期中的任何时候被多个线程访问。
 
更多 参考final的内存模型: