多線程設(shè)計模式解讀—Immutable Object(不可變對象)模式
前面講了Producer-Consumer模式利赋,它有許多變種搜立,我們以后會講席噩。我們將接著了解另外一個分支的設(shè)計模式蛾坯,前面所講的所有的模式丑搔,都是要用到鎖的,而鎖是會帶來一些額外的開銷和問題的高镐,那么能不能不通過鎖溉旋,實現(xiàn)多線程環(huán)境下的線程安全呢?其中一個思路就是通過Immutable Object(不可變對象)模式嫉髓。它使用對外可見的不可變對象观腊,天生具有線程安全的“基因”。因為與多線程的原子性算行、可見性相關(guān)的問題(如失效數(shù)據(jù)梧油、丟失更新操作、對象處于不一致狀態(tài)等)都與多線程試圖同時訪問同一個可變狀態(tài)相關(guān)州邢,若對象狀態(tài)不可變儡陨,那這些問題也就不存在了。
不可變對象的條件:
- 對象創(chuàng)建以后其狀態(tài)就不能修改
對象的所有域都是final類型
對象是正確創(chuàng)建的(對象創(chuàng)建期間,this引用沒有逸出)
構(gòu)造不可變對象建議:
- 類聲明為final類型迄委,字段可見性設(shè)置為private褐筛,這樣可以防止子類修改其字段值类少。
- 字段聲明為final字段叙身,這樣字段被賦值一次,就不會再被賦值硫狞。同時保證了字段引用的對象的初始化安全信轿。
- 不存在setter方法,且確保字段引用的實例未變化残吩。
示例代碼實例如下:
public final class ImmutableCustomer {
private final String name;
private final String address;
public ImmutableCustomer(String name, String address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public String getAddress() {
return address;
}
@Override
public String toString() {
return "[ ImmutableCustomer: name = " + name + ", address = " + address + " ]";
}
}
public class OpeCustomerThread extends Thread {
private ImmutableCustomer immutableCustomer;
public OpeCustomerThread(ImmutableCustomer person) {
this.immutableCustomer = person;
}
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName() + " "
+ immutableCustomer);
}
}
}
public class Main {
public static void main(String[] args) {
ImmutableCustomer alice = new ImmutableCustomer("Alice", "Alaska");
alice = new ImmutableCustomer("Ace", "Alaska");
new OpeCustomerThread(alice).start();
new OpeCustomerThread(alice).start();
new OpeCustomerThread(alice).start();
}
}
jdk中的CopyOnWriteArrayList也使用了該模式财忽,它是ArrayList的線程安全變體,其中所有變更操作(添加泣侮,設(shè)置等)都是通過創(chuàng)建底層數(shù)組的新副本來實現(xiàn)的(實際上即彪,array的元素是可以被替換的,這是一個事實不可變對象活尊,即對象從技術(shù)上而言未滿足不可變對象的嚴格定義隶校,是可變,但其狀態(tài)在安全發(fā)布后不會再改變了)蛹锰。這需要一定開銷深胳,但是當遍歷操作遠比變更頻繁時,它可能比其他方法更有效铜犬。它不需要加鎖就可以排除并發(fā)線程之間的干擾舞终。迭代器不會拋出ConcurrentModificationException。自迭代器創(chuàng)建后癣猾,迭代器無需考慮后期修改操作帶來的影響敛劝。
源碼片段如下:
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
private static final long serialVersionUID = 8673264195747942595L;
/** 鎖保護所有的變更操作 */
final transient ReentrantLock lock = new ReentrantLock();
/** array,只能通過getArray/setArray訪問 */
private transient volatile Object[] array;
/**
* 獲取array
*/
final Object[] getArray() {
return array;
}
/**
* 設(shè)置array.
*/
final void setArray(Object[] a) {
array = a;
}
/**
* 添加特定元素到list
*/
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
/**
* 移除特定位置的元素
*/
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
/**
* 返回的迭代器提供構(gòu)造迭代器時list狀態(tài)的快照纷宇。遍歷迭代器時不需要同步攘蔽。 迭代器不支持remove方法。
*/
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
}
從對以往CopyOnWriteArrayList使用呐粘,我們可以總結(jié)使用不可變對象模式需要注意的地方:
1满俗、當變更操作比較頻繁時,會在狀態(tài)變化時不斷創(chuàng)建替換新的不可變對象作岖,這會加重GC的負擔和系統(tǒng)開銷唆垃,應(yīng)該謹慎使用。
2痘儡、CopyOnWriteArrayList中array的元素是可以被替換的辕万,訪問其中的元素需要避免外部代碼修改其狀態(tài),這里的迭代器不支持remove方法。類似的情況渐尿,如我們返回HashMap類型的對象時醉途,需要做好防御性復(fù)制:
Collections.unmodifiableMap(deepCopy(map))
歡迎掃碼關(guān)注公眾號java達人: