為什么ImmutableMap是不可變集合,是線程安全的?
首先介紹一下基本概念,什么事immutable
何為Immutable對象?
簡單地說,如果一個對象實(shí)例不能被更改就是一個Immutable的對象,Java SDK提供的大量值對象,比如String等都是Immutable的對象。
如何使對象Immutable?
按照Effective Java的說明,需要滿足下面幾條規(guī)則:
- 保證類不能被繼承 - 為了避免其繼承的類進(jìn)行mutable的操作
- 移調(diào)所有setter/update等修改對象實(shí)例的操作
- 保證所有的field是private和final的
不可變對象(immutable objects),后面文章我將使用immutable objects來代替不可變對象!
那么什么是immutable objects?什么又是mutable Objects呢?
immutable Objects就是那些一旦被創(chuàng)建,它們的狀態(tài)就不能被改變的Objects,每次對他們的改變都是產(chǎn)生了新的immutable的對象,而mutable Objects就是那些創(chuàng)建后,狀態(tài)可以被改變的Objects.
舉個例子:String和StringBuilder,String是immutable的,每次對于String對象的修改都將產(chǎn)生一個新的String對象,而原來的對象保持不變,而StringBuilder是mutable,因?yàn)槊看螌τ谒膶ο蟮男薷亩甲饔糜谠搶ο蟊旧恚]有產(chǎn)生新的對象。
但有的時候String的immutable特性也會引起安全問題,這就是密碼應(yīng)該存放在字符數(shù)組中而不是String中的原因!
immutable objects 比傳統(tǒng)的mutable對象在多線程應(yīng)用中更具有優(yōu)勢,它不僅能夠保證對象的狀態(tài)不被改變,而且還可以不使用鎖機(jī)制就能被其他線程共享。
實(shí)際上JDK本身就自帶了一些immutable類,比如String,Integer以及其他包裝類。為什么說String是immutable的呢?比如:java.lang.String 的trim,uppercase,substring等方法,它們返回的都是新的String對象,而并不是直接修改原來的對象。
如何在Java中寫出Immutable的類?
要寫出這樣的類,需要遵循以下幾個原則:
1)immutable對象的狀態(tài)在創(chuàng)建之后就不能發(fā)生改變,任何對它的改變都應(yīng)該產(chǎn)生一個新的對象。
2)Immutable類的所有的屬性都應(yīng)該是final的。
3)對象必須被正確的創(chuàng)建,比如:對象引用在對象創(chuàng)建過程中不能泄露(leak)。
4)對象應(yīng)該是final的,以此來限制子類繼承父類,以避免子類改變了父類的immutable特性。
5)如果類中包含mutable類對象,那么返回給客戶端的時候,返回該對象的一個拷貝,而不是該對象本身(該條可以歸為第一條中的一個特例)
當(dāng)然不完全遵守上面的原則也能夠創(chuàng)建immutable的類,比如String的hashcode就不是final的,但它能保證每次調(diào)用它的值都是一致的,無論你多少次計算這個值,它都是一致的,因?yàn)檫@些值的是通過計算final的屬性得來的!
有時候你要實(shí)現(xiàn)的immutable類中可能包含mutable的類,比如java.util.Date,盡管你將其設(shè)置成了final的,但是它的值還是可以被修改的,為了避免這個問題,我們建議返回給用戶該對象的一個拷貝,這也是Java的最佳實(shí)踐之一。
使用Immutable類的好處:1)Immutable對象是線程安全的,可以不用被synchronize就在并發(fā)環(huán)境中共享 2)Immutable對象簡化了程序開發(fā),因?yàn)樗鼰o需使用額外的鎖機(jī)制就可以在線程間共享
3)Immutable對象提高了程序的性能,因?yàn)樗鼫p少了synchroinzed的使用
4)Immutable對象是可以被重復(fù)使用的,你可以將它們緩存起來重復(fù)使用,就像字符串字面量和整型數(shù)字一樣。你可以使用靜態(tài)工廠方法來提供類似于valueOf()這樣的方法,它可以從緩存中返回一個已經(jīng)存在的Immutable對象,而不是重新創(chuàng)建一個。
immutable也有一個缺點(diǎn)就是會制造大量垃圾,由于他們不能被重用而且對于它們的使用就是”用“然后”扔“,字符串就是一個典型的例子,它會創(chuàng)造很多的垃圾,給垃圾收集帶來很大的麻煩。當(dāng)然這只是個極端的例子,合理的使用immutable對象會創(chuàng)造很大的價值。
Guava提供的ImmutableMap是一個支持多線程環(huán)境下面的安全的Map,同時效率也是很高的Key-Value集合,為什么他就是安全的呢。
先看下面例子:
ImmutableMap.Builder<String, Object> request = ImmutableMap.builder();
request.put("one","1");
request.put("two","2");
request.put("three","3");
Map<String, Object> map = request.build();
讓我們首先從Builder<T,T>入手進(jìn)行分析
public static class Builder<K, V> {
TerminalEntry<K, V>[] entries;
int size;
public ImmutableMap<K, V> build() {
switch (size) {
case 0:
return of();
case 1:
return of(entries[0].getKey(), entries[0].getValue());
default:
return new RegularImmutableMap<K, V>(size, entries);
}
}
}
上面的
ImmutableMap.Builder<String, Object> request = ImmutableMap.builder();
這個實(shí)例創(chuàng)建的時候,只是創(chuàng)建了一個空的對象。
那么實(shí)際效用的是build()
@SuppressWarnings("unchecked")
Builder(int initialCapacity) {
this.entries = new TerminalEntry[initialCapacity];
this.size = 0;
}