閱讀826 返回首頁    go 阿裏雲 go 技術社區[雲棲]


線程安全及不可變性

當多個線程同時訪問同一個資源,並且其中的一個或者多個線程對這個資源進行了寫操作,才會產生競態條件。多個線程同時讀同一個資源不會產生競態條件。

我們可以通過創建不可變的共享對象來保證對象在線程間共享時不會被修改,從而實現線程安全。如下示例:


public class ImmutableValue{
	private int value = 0;

	public ImmutableValue(int value){
		this.value = value;
	}

	public int getValue(){
		return this.value;
	}
}

請注意ImmutableValue類的成員變量value是通過構造函數賦值的,並且在類中沒有set方法。這意味著一旦ImmutableValue實例被創建,value變量就不能再被修改,這就是不可變性。但你可以通過getValue()方法讀取這個變量的值。

譯者注:注意,“不變”(Immutable)和“隻讀”(Read Only)是不同的。當一個變量是“隻讀”時,變量的值不能直接改變,但是可以在其它變量發生改變的時候發生改變。比如,一個人的出生年月日是“不變”屬 性,而一個人的年齡便是“隻讀”屬性,但是不是“不變”屬性。隨著時間的變化,一個人的年齡會隨之發生變化,而一個人的出生年月日則不會變化。這就是“不 變”和“隻讀”的區別。(摘自《Java與模式》第34章)

如果你需要對ImmutableValue類的實例進行操作,可以通過得到value變量後創建一個新的實例來實現,下麵是一個對value變量進行加法操作的示例:


public class ImmutableValue{
	private int value = 0;

	public ImmutableValue(int value){
		this.value = value;
	}

	public int getValue(){
		return this.value;
	}

	public ImmutableValue add(int valueToAdd){
		return new ImmutableValue(this.value + valueToAdd);
	}
}

請注意add()方法以加法操作的結果作為一個新的ImmutableValue類實例返回,而不是直接對它自己的value變量進行操作。

引用不是線程安全的!

重要的是要記住,即使一個對象是線程安全的不可變對象,指向這個對象的引用也可能不是線程安全的。看這個例子:


public void Calculator{
	private ImmutableValue currentValue = null;

	public ImmutableValue getValue(){
		return currentValue;
	}

	public void setValue(ImmutableValue newValue){
		this.currentValue = newValue;
	}

	public void add(int newValue){
		this.currentValue = this.currentValue.add(newValue);
	}
}

Calculator類持有一個指向ImmutableValue實例的引用。注意,通過setValue()方法和add()方法可能會改變這個 引用。因此,即使Calculator類內部使用了一個不可變對象,但Calculator類本身還是可變的,因此Calculator類不是線程安全 的。換句話說:ImmutableValue類是線程安全的,但使用它的類不是。當嚐試通過不可變性去獲得線程安全時,這點是需要牢記的。

要使Calculator類實現線程安全,將getValue()、setValue()和add()方法都聲明為同步方法即可。


文章轉自 並發編程網-ifeve.com

最後更新:2017-05-23 09:31:43

  上一篇:go  采用Java 8中Lambda表達式和默認方法的模板方法模式
  下一篇:go  Guava 是個風火輪之基礎工具(2)