hashmap效率高單線程不安全,hashTable效率低但線程安全html
由於hashTable使用synchronized來保證線程安全,因此效率十分低,好比線程1使用put插入數據時,線程2既不能使用put插入,也不能使用get獲取。java
鎖分段技術
HashTable容器在競爭激烈的併發環境下表現出效率低下的緣由,是由於全部訪問HashTable的線程都必須競爭同一把鎖,那假如容器裏有多把鎖,每一把鎖用於鎖容器其中一部分數據,那麼當多線程訪問容器裏不一樣數據段的數據時,線程間就不會存在鎖競爭,從而能夠有效的提升併發訪問效率,這就是ConcurrentHashMap所使用的鎖分段技術,首先將數據分紅一段一段的存儲,而後給每一段數據配一把鎖,當一個線程佔用鎖訪問其中一個段數據的時候,其餘段的數據也能被其餘線程訪問。有些方法須要跨段,好比size()和containsValue(),它們可能須要鎖定整個表而而不只僅是某個段,這須要按順序鎖定全部段,操做完畢後,又按順序釋放全部段的鎖。這裏「按順序」是很重要的,不然極有可能出現死鎖,在ConcurrentHashMap內部,段數組是final的,而且其成員變量實際上也是final的,可是,僅僅是將數組聲明爲final的並不保證數組成員也是final的,這須要實現上的保證。這能夠確保不會出現死鎖,由於得到鎖的順序是固定的。算法
ConcurrentHashMap是由Segment數組結構和HashEntry數組結構組成。Segment是一種可重入鎖ReentrantLock,在ConcurrentHashMap裏扮演鎖的角色,HashEntry則用於存儲鍵值對數據。一個ConcurrentHashMap裏包含一個Segment數組,Segment的結構和HashMap相似,是一種數組和鏈表結構, 一個Segment裏包含一個HashEntry數組,每一個HashEntry是一個鏈表結構的元素, 每一個Segment守護者一個HashEntry數組裏的元素,當對HashEntry數組的數據進行修改時,必須首先得到它對應的Segment鎖。數據庫
concurrentHashMap轉自https://www.cnblogs.com/ITtangtang/p/3948786.html編程
併發編程的缺點
時間片是CPU分配給各個線程的時間,由於時間很是短,因此須要CPU不斷地切換線程,讓咱們以爲多個線程是同時運行的。並且每次切換時都須要把當前的狀態保存起來,以便下次切換過來時能夠恢復到先前執行時的狀態,而這個切換很是損耗性能,過於頻繁反而沒法發揮出多線程的優點。一般減小上下文切換能夠採用無鎖併發編程,CAS算法,使用最少的線程和使用協程。數組
無鎖併發編程:能夠參照concurrentHashMap鎖分段的思想,不一樣的線程處理不一樣段的數據,這樣在多線程競爭的條件下,能夠減小上下文切換的時間。安全
CAS算法,利用Atomic下使用CAS算法來更新數據,使用了樂觀鎖,能夠有效的減小一部分沒必要要的鎖競爭帶來的上下文切換多線程
使用最少線程:避免建立不須要的線程,好比任務不多,可是建立了不少的線程,這樣會形成大量的線程都處於等待狀態併發
協程:在單線程裏實現多任務的調度,並在單線程裏維持多個任務間的切換ide
因爲上下文切換也是個相對比較耗時的操做,因此在"java併發編程的藝術"一書中有過一個實驗,併發累加未必會比串行累加速度要快。 可使用Lmbench3測量上下文切換的時長 vmstat測量上下文切換次數
線程安全
多線程編程中最難以把握的就是臨界區線程安全問題,稍微不注意就會出現死鎖的狀況,一旦產生死鎖就會形成系統功能不可用。
public class DeadLockDemo { private static String resource_a = "A"; private static String resource_b = "B"; public static void main(String[] args) { deadLock(); } public static void deadLock() { Thread threadA = new Thread(new Runnable() { @Override public void run() { synchronized (resource_a) { System.out.println("get resource a"); try { Thread.sleep(3000); synchronized (resource_b) { System.out.println("get resource b"); } } catch (InterruptedException e) { e.printStackTrace(); } } } }); Thread threadB = new Thread(new Runnable() { @Override public void run() { synchronized (resource_b) { System.out.println("get resource b"); synchronized (resource_a) { System.out.println("get resource a"); } } } }); threadA.start(); threadB.start(); } }
在上面的這個demo中,開啓了兩個線程threadA, threadB,其中threadA佔用了resource_a, 並等待被threadB釋放的resource _b。threadB佔用了resource _b正在等待被threadA釋放的resource _a。所以threadA,threadB出現線程安全的問題,造成死鎖。一樣能夠經過jps,jstack證實這種推論:
"Thread-1": waiting to lock monitor 0x000000000b695360 (object 0x00000007d5ff53a8, a java.lang.String), which is held by "Thread-0" "Thread-0": waiting to lock monitor 0x000000000b697c10 (object 0x00000007d5ff53d8, a java.lang.String), which is held by "Thread-1" Java stack information for the threads listed above: =================================================== "Thread-1": at learn.DeadLockDemo$2.run(DeadLockDemo.java:34) - waiting to lock <0x00000007d5ff53a8(a java.lang.String) - locked <0x00000007d5ff53d8(a java.lang.String) at java.lang.Thread.run(Thread.java:722) "Thread-0": at learn.DeadLockDemo$1.run(DeadLockDemo.java:20) - waiting to lock <0x00000007d5ff53d8(a java.lang.String) - locked <0x00000007d5ff53a8(a java.lang.String) at java.lang.Thread.run(Thread.java:722) Found 1 deadlock.
如上所述,徹底能夠看出當前死鎖的狀況。
那麼,一般能夠用以下方式避免死鎖的狀況:
- 避免一個線程同時得到多個鎖;
- 避免一個線程在鎖內部佔有多個資源,儘可能保證每一個鎖只佔用一個資源;
- 嘗試使用定時鎖,使用lock.tryLock(timeOut),當超時等待時當前線程不會阻塞;
- 對於數據庫鎖,加鎖和解鎖必須在一個數據庫鏈接裏,不然會出現解鎖失敗的狀況
因此,如何正確的使用多線程編程技術有很大的學問,好比如何保證線程安全,如何正確理解因爲JMM內存模型在原子性,有序性,可見性帶來的問題,好比數據髒讀,DCL等這些問題(在後續篇幅會講述)。而在學習多線程編程技術的過程當中也會讓你收穫頗豐。