|
我們平時編碼時使用集合類,都是new 一個 ArrayList 或者 HashSet 或者 HashMap就直接開用,好像也沒遇到啥問題。但是多線程情況下就會有問題。下面一一道來。 ![]() ![]() 1、故障現(xiàn)象: 這段代碼很簡單,就是創(chuàng)建30個線程,每個線程往list集合add元素,看似沒啥問題,看代碼的運行結果: ![]()
2、導致原因: 3、解決方案:
這個方法顧名思義,就是可以把ArrayList變成安全的。所以它也可以解決并發(fā)修改異常。
就是new 一個 CopyOnWriteArrayList就可以了。那么這個類為什么能保證線程安全呢?看一下它的源碼: 所謂寫時復制,就是寫的時候不是直接在原來的數(shù)組中寫,而是先復制一份,寫完后再引用這個新的。還是簽名的例子:老師說同學們一個個地上來簽名。張三上去了,把那份名單copy了一份,簽上了自己的名字。在張三簽名的過程中,其他同學還是可以讀老師的那份名單的。當張三簽完了,然后再告訴同學們,之前那份名單作廢了,現(xiàn)在用這份新的。這就是整個過程,對應了上面的代碼。首先用lock鎖住這段代碼,即張三簽名過程中其他同學不能再來搶筆了;然后獲取到原來的數(shù)組,定義一個新數(shù)組,長度為原來的數(shù)組加1,把原數(shù)組內(nèi)容復制到新數(shù)組中,這是張三復制名單的過程;然后將要add的元素添加到新數(shù)組的最后,這就是張三寫自己名字的過程;再后來將引用指向新數(shù)組,這是張三告訴大家用這份新名單的過程;最后釋放鎖,也就是張三把筆放下,下一個同學可以去簽名了。 ![]() ![]() ![]()
1、故障現(xiàn)象: 把上面的ArrayList換成HashSet,一樣會報并發(fā)修改異常。導致原因也是一樣的,下面直接看看解決原因。 2、解決方案:
![]() ![]() ![]() Map集合同樣會出現(xiàn)上述問題。很容易讓人想到解決方案也是和上面一樣,其實有點區(qū)別。首先,的確可以使用Collections工具類的synchronizedMap方法,其次,也可以使用HashTable。HashTable所有的方法都加了鎖,所以可以保證安全。但是也正因它所有方法都加了鎖,并發(fā)性不好,所以不推薦使用。第三種辦法,可能會想到寫時復制,其實java沒有為map提供寫時復制的類。我們可以使用ConcurrentHashMap,這個也是線程安全的,而且性能還不錯。它是使用了CAS來保證安全性。我另一篇文章《Java源碼解讀---HashMap&ConcurrentHashMap》中有介紹,大家可以參考一下。
首先它判斷你new的集合有沒有實現(xiàn)RandomAccess接口 (這個接口是一個標記接口,ArrayList就實現(xiàn)了這個接口。作用就是,如果實現(xiàn)了這個接口,那么就說明支持快速隨機訪問,如果支持快速隨機方法,那么取元素的時候就用for循環(huán),否則就用迭代器。這是因為,如果不支持快速隨機訪問,用迭代器獲取元素效率會更高。ArrayList由數(shù)組實現(xiàn),可以通過索引獲取元素,顯然是支持快速隨機訪問) 。然后 new SynchronizedRandomAccessList<>(list);其實就是對傳進去的list的方法加上了同步代碼塊,所以可以保證線程安全。它和Vector、HashTable的區(qū)別也就在于,它使用的是同步代碼塊,而后兩者使用的是同步方法。 ![]() |
|
|