不可變對象要滿足的條件:
- 1.對象創(chuàng)建以后其狀態(tài)不能修改(話外音:final數(shù)據(jù)可以是編譯時不改變值的常量,在對這個常量進行定義的時候,必須對其賦值。變量的值一旦初始化,就不能改變。如果final變量是引用類型的變量,那么代表你不能去改變它的引用,一旦引用指向被初始化指向一個對象,就無法再把它改為指向另一個對象,但是可以改變這個變量所引用的對象的屬性)
- 2.對象所有域都是final類型。
- 3.對象是正確創(chuàng)建的(在對象創(chuàng)建期間,this引用沒有逸出)。
Collections.unmodifiableXXX:Collection、List、Set、Map...
Guava: ImmutabeXXX:Collection、List、Set、Map...
這些方法都能將指定對象設置為不可變,只讀。
newHashMap已經(jīng)是不可變,不能再添加新元素了。如果再進行put操作,會拋出java.lang.UnsupportedOperationException異常。
public class C {
public static void main(String[] args) {
Map<String, String> hashMap = Maps.newHashMap();
hashMap.put("boy", "cmazxiaoma");
Map<String, String> newHashMap = Collections.unmodifiableMap(hashMap);
newHashMap.put("girl", "doudou");
}
}
這里提一個概念"線程封閉":就是將對象封裝到一個線程里,只有一個線程能看到這個對象,就算這個對象就算不是線程安全的,也不會出現(xiàn)線程安全方面的問題。SpringMVC、Shiro框架都用到了線程封閉的思想。
堆棧封閉:就是方法中定義局部變量,不存在并發(fā)問題,因為是線程私有的。多個線程訪問一個方法時,方法中的布局變量都會被拷貝一份到線程的棧中,所以局部變量時不會被多個線程所共享的。
ThreadLocal線程封閉:這就不多說了(之前文章提到過)。ThreadLocal為每個線程提供了一個變量副本,從而解決了多線程訪問變量的沖突。話外音:如果系統(tǒng)CPU資源有空閑,但是內(nèi)存使用緊張,可以考慮使用時間換空間的策略(synchronized就是采取該策略)。反之,CPU資源緊張,內(nèi)存資源空閑,則可以使用空間換時間策略(ThreadLocal就是采取該策略),提高整體性能。

SimpleDateFormat是線程不安全的,可以通過ThreadLocal或者JDK1.8的LocalDateTime和DateTimeFormatter。
public class MyThreadLocal {
public static final ThreadLocal<DateFormat> dateFormatThreadLocal = new ThreadLocal<DateFormat>(){
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static String format(Date date) {
return dateFormatThreadLocal.get().format(date);
}
public static Date parse(String date) throws ParseException {
return dateFormatThreadLocal.get().parse(date);
}
public static void main(String[] args) {
String date = format(new Date());
System.out.println(date);
}
}
public class LocalTimeTest {
public static void main(String[] args) {
// jdk1.8 線程安全獲取系統(tǒng)時間
System.out.println(LocalDateTime.now());
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// parse
LocalDateTime localDateTime = LocalDateTime.parse("2018-11-23 21:58:00", dateTimeFormatter);
System.out.println(localDateTime);
// format
System.out.println(LocalDateTime.now().format(dateTimeFormatter));
}
}
Java中是有提供了同步容器的,不過性能很差。
ArrayList ===>Vector, Stack
image.png
image.png
HashMap(key和value都能為空) ===> Hashtable(key,value不能為空)
寫到這里,就來講講HashMap和Hashtable之間的區(qū)別把:
1.Hashtable中key和value都不允許出現(xiàn)null值,但是在HashMap中null是可以作為key的,這樣的key只有一個??梢杂幸粋€key或者多個key所對應的值為null。當get()方法返回null時,即可以表示HashMap中沒有該key,也可以表示該key所對應的值為null。因此在HashMap中不能由get()方法來判斷HashMap是否存在某個key,而應該用containsKey()來判斷。
2.兩個在遍歷方式的內(nèi)部實現(xiàn)上有所不同。兩者都使用了Iterator,而Hashtable由于歷史原因,還使用了Enumeration。
3.哈希值的使用不同。Hashtable直接使用了對象的hashcode,而HashMap需要重新計算hash值。
4.兩者內(nèi)部實現(xiàn)的數(shù)組的初始化大小和擴容方式不同。Hashtable中hash數(shù)組默認大小是11,擴容方式是2*old+1。而HashMap中hash數(shù)組默認大小是16,擴容后大小一定是2的指數(shù)。
5.HashSet依靠hashCode()和equals()方法來區(qū)分重復元素。HashSet內(nèi)部使用Map來保存數(shù)據(jù)。HashSet的數(shù)據(jù)作為Map中的key值保存,這也是HashSet中元素不能重復的原因。而Map保存key值時,會去判斷當前Map中是否會有該key。先通過key的hashcode,如果有相同的hashcode(hash沖突),再用equals()方法判斷是否相同。
Collections.synchronizedXXX(List、Set、Map)
雖然說Vector是同步容器,但是也無法保證多線程讀寫數(shù)據(jù)情況下的線程安全(不支持邊遍歷邊修改)。
public class VectorTest {
private static Vector<Long> vector = new Vector<>();
public static void main(String[] args) {
for (;;) {
for (int i = 0; i < 10; i++) {
vector.add(i + 1L);
}
Thread getThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < vector.size(); i++) {
vector.get(i);
}
}
});
Thread removeThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0 ; i < vector.size(); i++) {
vector.remove(i);
}
}
});
getThread.start();
removeThread.start();
}
}
}

來看看并發(fā)容器把。
ArrayList對應著CopyOnWriteArrayList
HashSet、TreeSet對應著CopyOnWriteArraySet、ConCurrentSkipListSet
HashMap、TreeMap對應著ConcurrentHashMap、ConcurrentSkipListMap
CopyOnWriteArrayList當有新元素的時候添加時,它會將原來數(shù)組拷貝一份。然后在新的數(shù)組上面做寫操作,寫完之后,將原來的數(shù)組指向新的數(shù)組。當元素的內(nèi)容過多的時候,會造成YGC或者FGC。因為拷貝數(shù)據(jù)是需要時間的,我們讀的可能不是最新的數(shù)據(jù),所以無法滿足實時讀。也就是說不是強一致性,而是追求最終一致性。CopyOnWriteArrayList用到了讀寫分離思想,適合讀多寫少的場景。CopyOnWriteArray讀是不需要加鎖的,是在原來的數(shù)組上面讀。而寫卻是要加鎖,避免多線程并發(fā)修改拷貝出多個副本。

