1. 程式人生 > >java基礎知識1:關鍵字;介面和抽象類;java併發相關

java基礎知識1:關鍵字;介面和抽象類;java併發相關

true、false、null都不是關鍵字 goto、const、是保留的關鍵字 abstract continue for new switch default if package synchronized do goto private this break double implements protected throw byte else import public throws case enum instanceof return transient catch extends int short try char final interface static void class finally long strictfp volatile const float native super while boolean assert
注意點:true false null 是值.是常量值.理解區分

部分差別 具體詳細區別:普通類,抽象類和介面比較 CopyOnWriteArrayList:執行緒安全 使用了一種叫寫時複製的方法,當有新元素新增到CopyOnWriteArrayList時,先從原有的陣列中拷貝一份出來,然後在新的陣列做寫操作,寫完之後,再將原來的陣列引用指向到新陣列。

當有新元素加入的時候,如下圖,建立新陣列,並往新陣列中加入一個新元素,這個時候,array這個引用仍然是指向原陣列的。

當元素在新陣列新增成功後,將array這個引用指向新陣列。

CopyOnWriteArrayList的整個add操作都是在鎖的保護下進行的。 這樣做是為了避免在多執行緒併發add的時候,複製出多個副本出來,把資料搞亂了,導致最終的陣列資料不是我們期望的。

CopyOnWriteArrayList的add操作的原始碼如下:

public boolean add(E e) { //1、先加鎖 final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; //2、拷貝陣列 Object[] newElements = Arrays.copyOf(elements, len + 1); //3、將元素加入到新陣列中 newElements[len] = e; //4、將array引用指向到新陣列 setArray(newElements); return true; } finally { //5、解鎖 lock.unlock(); } }

由於所有的寫操作都是在新陣列進行的,這個時候如果有執行緒併發的寫,則通過鎖來控制,如果有執行緒併發的讀,則分幾種情況: 1、如果寫操作未完成,那麼直接讀取原陣列的資料; 2、如果寫操作完成,但是引用還未指向新陣列,那麼也是讀取原陣列資料; 3、如果寫操作完成,並且引用已經指向了新的陣列,那麼直接從新陣列中讀取資料。

可見,CopyOnWriteArrayList的讀操作是可以不用加鎖的。 全文解釋地址 本質上來講還是使用了lock鎖住了add方法,所以在寫方面是執行緒安全的 通過上面的分析,CopyOnWriteArrayList 有幾個缺點: 1、由於寫操作的時候,需要拷貝陣列,會消耗記憶體,如果原陣列的內容比較多的情況下,可能導致young gc或者full gc

2、不能用於實時讀的場景,像拷貝陣列、新增元素都需要時間,所以呼叫一個set操作後,讀取到資料可能還是舊的,雖然CopyOnWriteArrayList 能做到最終一致性,但是還是沒法滿足實時性要求;

CopyOnWriteArrayList 合適讀多寫少的場景,不過這類慎用 因為誰也沒法保證CopyOnWriteArrayList 到底要放置多少資料,萬一資料稍微有點多,每次add/set都要重新複製陣列,這個代價實在太高昂了。在高效能的網際網路應用中,這種操作分分鐘引起故障。 CopyOnWriteArrayList透露的思想 如上面的分析CopyOnWriteArrayList表達的一些思想: 1、讀寫分離,讀和寫分開 2、最終一致性 3、使用另外開闢空間的思路,來解決併發衝突 ReadWriteLock: ReadWriteLock維護了一對鎖,讀鎖可允許多個讀執行緒併發使用,寫鎖是獨佔的。適用於讀多寫少的情況.讀寫鎖的效率明顯高於synchronized關鍵字(synchronizes不允許併發讀) 下面對鎖的相關概念做一個介紹: 1.可重入(Reentrant)鎖 如果鎖具備可重入性,則稱作為可重入鎖。像synchronized和 ReentrantLock都是可重入鎖,可重入性在我看來實際上表明瞭鎖的分配機制:基於執行緒的分配,而不是基於方法呼叫的分配。舉個簡單的例子,當一 個執行緒執行到某個synchronized方法時,比如說method1,而在method1中會呼叫另外一個synchronized方法 method2,此時執行緒不必重新去申請鎖,而是可以直接執行方法method2。 看下面這段程式碼就明白了: classMyClass { public synchronized void method1() { method2(); } public synchronized void method2() { }} 上述程式碼中的兩個方法method1和method2都用synchronized修飾了,假如某一時刻,執行緒A執行到了method1,此時執行緒 A獲取了這個物件的鎖,而由於method2也是synchronized方法,假如synchronized不具備可重入性,此時執行緒A需要重新申請 鎖。但是這就會造成一個問題,因為執行緒A已經持有了該物件的鎖,而又在申請獲取該物件的鎖,這樣就會執行緒A一直等待永遠不會獲取到的鎖。  而由於synchronized和Lock都具備可重入性,所以不會發生上述現象。 2.可中斷鎖   可中斷鎖:顧名思義,就是可以相應中斷的鎖。   在Java中,synchronized就不是可中斷鎖,而Lock是可中斷鎖。   如果某一執行緒A正在執行鎖中的程式碼,另一執行緒B正在等待獲取該鎖,可能由於等待時間過長,執行緒B不想等待了,想先處理其他事情,我們可以讓它中斷自己或者在別的執行緒中中斷它,這種就是可中斷鎖。 3.公平鎖   公平鎖即儘量以請求鎖的順序來獲取鎖。比如同是有多個執行緒在等待一個鎖,當這個鎖被釋放時,等待時間最久的執行緒(最先請求的執行緒)會獲得該所,這種就是公平鎖。   非公平鎖即無法保證鎖的獲取是按照請求鎖的順序進行的。這樣就可能導致某個或者一些執行緒永遠獲取不到鎖。   在Java中,synchronized就是非公平鎖,它無法保證等待的執行緒獲取鎖的順序。 而對於ReentrantLock和ReentrantReadWriteLock,它預設情況下是非公平鎖,但是可以設定為公平鎖。設定方法如下:ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true); 4、讀寫鎖(已經介紹過)

ConcurrentHashMap介紹 執行緒不安全的HashMap 因為多執行緒環境下,使用Hashmap進行put操作會引起死迴圈,導致CPU利用率接近100%,所以在併發情況下不能使用HashMap。

效率低下的HashTable容器 HashTable容器使用synchronized來保證執行緒安全,但線上程競爭激烈的情況下HashTable的效率非常低下。因為當一個執行緒訪問HashTable的同步方法時,其他執行緒訪問HashTable的同步方法時,可能會進入阻塞或輪詢狀態。如執行緒1使用put進行新增元素,執行緒2不但不能使用put方法新增元素,並且也不能使用get方法來獲取元素,所以競爭越激烈效率越低。

鎖分段技術 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鎖。 應用場景 當有一個大陣列時需要在多個執行緒共享時就可以考慮是否把它給分層多個節點了,避免大鎖。並可以考慮通過hash演算法進行一些模組定位。 其實不止用於執行緒,當設計資料表的事務時(事務某種意義上也是同步機制的體現),可以把一個表看成一個需要同步的陣列,如果操作的表資料太多時就可以考慮事務分離了(這也是為什麼要避免大表的出現),比如把資料進行欄位拆分,水平分表等. 原始碼解讀 ConcurrentHashMap(1.7及之前)中主要實體類就是三個:ConcurrentHashMap(整個Hash表),Segment(桶),HashEntry(節點),對應上面的圖可以看出之間的關係

/** 
* The segments, each of which is a specialized hash table 
*/  
final Segment<K,V>[] segments;

不變(Immutable)和易變(Volatile) ConcurrentHashMap完全允許多個讀操作併發進行,讀操作並不需要加鎖。如果使用傳統的技術,如HashMap中的實現,如果允許可以在hash鏈的中間新增或刪除元素,讀操作不加鎖將得到不一致的資料。ConcurrentHashMap實現技術是保證HashEntry幾乎是不可變的。HashEntry代表每個hash鏈中的一個節點,其結構如下所示:

 static final class HashEntry<K,V> {  
     final K key;  
     final int hash;  
     volatile V value;  
     final HashEntry<K,V> next;  
 } 

可以看到除了value不是final的,其它值都是final的,這意味著不能從hash鏈的中間或尾部新增或刪除節點,因為這需要修改next 引用值,所有的節點的修改只能從頭部開始。對於put操作,可以一律新增到Hash鏈的頭部。但是對於remove操作,可能需要從中間刪除一個節點,這就需要將要刪除節點的前面所有節點整個複製一遍,最後一個節點指向要刪除結點的下一個結點。這在講解刪除操作時還會詳述。為了確保讀操作能夠看到最新的值,將value設定成volatile,這避免了加鎖。 volatile(揮發性的;不穩定的;爆炸性的;反覆無常的): olatite只保證執行緒在“載入資料階段”載入的資料是最新的,並不能保證執行緒安全。 一個執行緒執行的過程有三個階段: 載入(複製)主存資料到操作棧 --> 對操作棧資料進行修改 --> 將操作棧資料寫回主存 volatite關鍵字,讓編譯器不去優化程式碼使用快取等,以保證執行緒在“載入資料階段”載入的資料都是最新的 比如: 某一時刻i=6是最新的值,volatile保證執行緒A,B都同時載入了這個最新的值, 然後A執行i(A)+1=7,然後將7寫回主存, B也執行i(B)+1=7,然後也將7寫回記憶體, 這樣,執行兩次加法,i卻只增加了1