业务的可变性和不可变性分析_不可变性真的意味着线程安全吗?
業務的可變性和不可變性分析
我經常閱讀有關“如果對象是不可變的,則它是線程安全的”的文章。 實際上,我從未找到過一篇使我相信不可變意味著線程安全的文章。 即使是Brian Goetz的Java Concurrency in Practice一書中關于不變性的一本書,也沒有完全使我滿意。 在這本書中,我們可以在一個框架中逐字閱讀: 不可變對象始終是線程安全的 。 我認為這句話值得更多解釋。 因此,我將嘗試定義不變性及其與線程安全性的關系。
定義 不變性
我的定義是“不可變的對象是在構造后狀態不會改變的對象”。 我故意含糊其詞,因為沒有人真正同意確切的定義。
線程安全
您可以在Internet上找到許多不同的“線程安全”定義。 定義它實際上非常棘手。 我會說線程安全代碼是在多線程環境中具有預期行為的代碼。 我讓您定義“預期行為”…
字符串示例
讓我們看一下String的代碼(實際上只是一部分代碼...):
public class String {private final char value[];/** Cache the hash code for the string */private int hash; // Default to 0public String(char[] value) {this.value = Arrays.copyOf(value, value.length);}public int hashCode() {int h = hash;if (h == 0 && value.length > 0) {char val[] = value;for (int i = 0; i < value.length; i++) {h = 31 * h + val[i];}hash = h;}return h;} }String被認為是不可變的。 看一下它的實現,我們可以推斷出一件事:不可變的對象可以更改其內部狀態(在本例中為延遲加??載的哈希碼),只要它在外部不可見即可。
現在,我將以一種非線程安全的方式重寫hashcode方法:
public int hashCode() {if (hash == 0 && value.length > 0) {char val[] = value;for (int i = 0; i < value.length; i++) {hash = 31 * hash + val[i];}}return hash;}如您所見,我刪除了局部變量h并直接影響了變量hash 。 此實現不是線程安全的! 如果多個線程同時調用hashcode ,則每個線程的返回值可能不同。 問題是,這堂課是一成不變的嗎? 由于兩個不同的線程可以看到不同的哈希碼,因此從外部角度來看,我們具有狀態更改,因此它不是不可變的。
我們可以得出這樣的結論: String是不可變的, 因為它是線程安全的,而不是相反的。 所以……說“做一些不可變的對象,它是線程安全的!”有什么意義? 但是請注意,您必須使不可變對象具有線程安全性!” ?
ImmutableSimpleDateFormat示例
在下面,我寫了一個類似于SimpleDateFormat的類。
public class VerySimpleDateFormat {private final DateFormat formatter = SimpleDateFormat.getDateInstance(SimpleDateFormat.SHORT);public String format(Date d){return formatter.format(d);} }該代碼不是線程安全的,因為SimpleDateFormat.format不是。
這個對象是不變的嗎? 好問題! 我們已盡最大努力使所有字段均不可修改,我們不使用任何建議設置對象狀態將改變的設置方法或方法。 實際上,內部SimpleDateFormat更改其狀態,這就是它不安全線程的原因。 由于對象圖中的某些內容發生了變化,因此即使它看起來像它,它也不是不變的。問題甚至不在于SimpleDateFormat更改其內部狀態,而是在于它以一種非線程安全的方式進行操作。
總結這個例子,創建一個不可變的類并不容易。 最后一個關鍵字還不夠,您必須確保對象的對象字段不會更改其狀態,這有時是不可能的。
不可變的對象可以具有非線程安全的方法(沒有魔術!)
讓我們看一下下面的代碼。
public class HelloAppender {private final String greeting;public HelloAppender(String name) {this.greeting = 'hello ' + name + '!\n';}public void appendTo(Appendable app) throws IOException {app.append(greeting);} } HelloAppender類絕對是不可變的。 方法appendTo接受Appendable 。 由于Appendable不能保證是線程安全的(例如StringBuilder ),因此附加到此Appendable會在多線程環境中引起問題。
結論
在某些情況下,創建不可變的對象絕對是一個好習慣,這對創建線程安全的代碼有很大幫助。 但是當我在任何地方閱讀時,我都感到困擾。 不可變對象是線程安全的 ,顯示為公理。 我明白了這一點,但是我認為思考一下這總是很好,以便了解導致非線程安全代碼的原因。
感謝Jose的評論,在本文的結尾我得出了不同的結論。 這都是關于不可變的定義。 需要澄清!
如果滿足以下條件,則對象是不可變的:
- 它的所有字段在使用之前都已初始化(這意味著您可以進行延遲初始化)
- 字段的狀態在初始化后不會更改(不更改表示對象圖不會更改,即使子級的內部狀態也是如此)
除非對象必須處理非線程安全的對象,否則不可變對象將始終是線程安全的。
參考: 不變性真的意味著線程安全嗎? 從我們的JCG合作伙伴 Tibo Delor在InvalidCodeException博客中獲得。
翻譯自: https://www.javacodegeeks.com/2012/09/does-immutability-really-means-thread.html
業務的可變性和不可變性分析
總結
以上是生活随笔為你收集整理的业务的可变性和不可变性分析_不可变性真的意味着线程安全吗?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑速度慢怎么办电脑速度慢如何解决
- 下一篇: 如何把iPhone的照片传到电脑上苹果手