關於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的對象。這種情況下,必須保證該對象的線程安全性:這和線程的並發訪問關系不大,主要是因為在其生命周期內,不同的線程會在任意時刻訪問它(還是內存模型的問題吧)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM