① volatile保證可見性 ,即它會保證修改的值當即被更新到主存中,當有其餘線程須要讀取時,它會去內存中讀取新值算法
(Java 內存模型規定了 全部的變量都存儲在主內存中,每條線程還有本身的工做內存,線程的工做內存中保存了被該線程所使用的到的變量,線程對變量的操做都必須在工做內存中進行,不一樣線程之間也沒法直接訪問對方工做內存中的變量,線程間變量值的傳遞須要經過主內存來完成)數組
② 禁止進行指令重排序緩存
使用volatile 必須具有如下2個條件:安全
對變量的寫操做不依賴當前值bash
該變量沒有包含在具備其餘變量的不變式中數據結構
線程池的優點:多線程
① 下降系統資源消耗,經過重用已存在的線程,下降線程建立和銷燬形成的消耗;併發
② 提升系統響應速度,當有任務到達時,無需等待新線程的建立便能當即執行;app
③ 方便線程併發數的管控,線程如果無限制的建立,不只會額外消耗大量系統資源,更是佔用過多資源而阻塞資源或oom等情況,從而下降系統的穩定性,線程池有效管控線程,統一分配、調優,提供資源使用率;ide
線程池關閉方式:
① shutdown() : 將線程池設置成shutdown狀態,而後中斷全部沒有正在執行任務的線程。
② sutdownNow(): 將線程池設置成stop狀態, 而後中斷全部任務(包括正在執行的任務)的線程,並返回等待執行任務的列表。
Java中有四種不一樣功能的線程池:
一、newFixedThreadPool : 該線程池鎖容納的最大線程數就是所設置的核心線程數,若是線程池中的線程處於空閒狀態,它們不會被回收,除非是這個線程池被關閉了,若是全部的線程都處於活動狀態,新任務就會處於等待狀態,直到有線程空閒出來。優點:可以更快速響應外界請求。
二、newCachedThreadPool : 該線程池中核心線程數爲0,最大線程數爲Integer.Max_Value,當線程池中的線程都處於活動狀態的時候,線程池就會建立一個新的線程來處理任務,該線程池中的線程超時時長爲60秒,,因此當線程處於閒置狀態超過60秒的時候就會被回收
三、newScheduledThreadPool : 核心線程數是固定的,當非核心線程處於限制狀態時會當即被回收,經常使用於執行定時任務。
四、newSingleThreadExecutor : 該線程池只有一個核心線程,對於任務隊列沒有大小限制,也就意味着這一任務處於活動狀態時,其餘任務都會在任務隊列中排隊等候依次執行。
synchronized void setA() throws Exception() {
Thread.sleep(1000);
setB();
}
synchronized void setB() throws Exception() {
Thread.sleep(1000);
}
複製代碼
1、Java 內存區域
2、Java 內存模型
Java 內存模型規定了全部的變量都存儲到主內存中,每條線程還有本身的工做內存,線程的工做內存中保存了被該線程使用到的變量,線程對變量的全部操做都必須在工做內存執行,不一樣線程之間也沒法直接訪問對方工做內存中的變量,線程間變量值的傳遞須要經過主內存來完成。
① 類加載機制定義:把描述類的數據從Class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終造成能夠被虛擬機直接使用的Java類型。
②雙親委派模型:若是一個類加載器收到了類加載的請求,它首先不會本身去嘗試加載這個類,而是把請求委託給父加載器去完成,依次向上,所以,全部的類加載請求最終都應該被傳遞到頂層的啓動類加載器(Bootstrap ClassLoader)中。只有當父加載器在它的搜索範圍內沒有找到所需的類時,子加載器纔會嘗試本身去加載該類。
這樣的好處是不一樣層次的類加載器具備不一樣優先級,好比全部Java對象的超級父類Object,位於rt.jar,不管哪一個類加載器加載該類,最終都是由啓動加載器進行加載,保證安全,若是開發者本身編寫一個Object類放入程序中,雖能正常編譯,但不會被加載運行,保證不會出現混亂。
標記-清除算法 ① 首先標記出全部須要回收的對象 ② 在標記完成後統一回收全部被標記的對象
缺點:標記和清除兩個過程效率不高,標記清除以後產生大量不連續的內存碎片,空間碎片太多可能會致使運行過程當中須要分配較大對象時,沒法找到足夠的連續內存而不得不提早觸發另外一次垃圾收集動做
複製算法
將可用內存按照容量大小劃分爲大小相等的兩塊,每次只使用其中的一塊,當一塊內存使用完了,就將還存活着的對象複製到另外一塊上面,而後把使用過的內存空間一次清理掉,這樣使得每次都是對整個半區進行內存回收,內存分配時也不用考慮內存碎片等複雜狀況。
缺點:將內存縮小爲原來的一半。
標記-整理算法 複製收集算法在對象存活率比較高時,就要進行比較多的複製操做,效率就會變低; 標記過程仍然與 「標記-清除」算法同樣,可是後續步驟不是直接對可回收對象進行清理,而是讓全部存活的對象都向一端移動,而後直接清理掉邊界之外的內存。
分代收集算法 通常把Java堆分爲新生代和老生代, 這樣就能夠根據各個年代的特色採用最適合的收集算法。
在新生代中,每次垃圾收集時都發現有大批對象死去,只有少許存活,那就選用複製算法。
在老生代中,由於對象存活率高,沒有額外空間對它進行分配擔保,就必須採用「標記-清除」或「標記-整理」算法來進行回收。
1、引用計數法
給對象添加一個引用計數器,每當有一個地方引用它時,計數器值就加1,當引用失效時,計數器值就減1;任什麼時候刻計數器爲0的對象就是不可能被再使用的。
主流的JVM裏面沒有選用引用計數法來管理內存,其中最主要的緣由就是它很難解決對象間的互循環引用的問題。
2、可達性分析算法
經過一些稱爲「GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用連,當一個對象到GC Roots沒有任何引用連相連時,證實此對象是不可用的,因此它們會被斷定爲可回收對象。如圖B的對象是不可達的
在Java中,能夠做爲GC Roots的對象包括下面幾種:
虛擬機棧(棧幀中的本地變量表)中引用的對象
方法區中靜態屬性引用的對象
方法區中常量引用的對象
本地方法棧JNI(通常說的Native方法)引用的對象
複製代碼
在可達性分析算法中,要真正宣告一個對象死亡, 至少要經歷兩次標記過程:
LruCache的核心思想就是要維護一個緩存對象列表,其中對象列表的排列方式是按照訪問順序實現的,即一直沒訪問的對象,放在隊尾,即將被淘汰,而最近訪問的對象放在對頭,最後被淘汰。 如圖所示:
這個隊列是由LinkedHashMap來維護的,而LinkedHashMap是由數組+雙向鏈表 的數據結構來實現的,其中雙向鏈表的結構能夠實現訪問順序和插入順序,使得LinkedHashMap中的key,value對按照必定順序排列起來。
經過下面構造函數來指定LinkedHashMap中雙向鏈表的結構是訪問順序仍是插入順序。
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
複製代碼
其中accessOrder設置爲true 則爲訪問順序,爲false,則爲插入順序。 以具體的例子解釋,當設置爲true 時
public static final void main(String[] args) {
LinkedHashMap<Integer, Integer> map = new LinkedHashMap<>(0, 0.75f, true);
map.put(0, 0);
map.put(1, 1);
map.put(2, 2);
map.put(3, 3);
map.put(4, 4);
map.put(5, 5);
map.put(6, 6);
map.get(1);
map.get(2);
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
}
複製代碼
輸出結果:
0:0
3:3
4:4
5:5
6:6
1:1
2:2
複製代碼
即最近訪問的最後輸出,那麼就正好知足的LRU緩存算法的思想,可見LruCache巧妙實現,就是利用了LinkedHashMap的這種數據結構。 下面咱們在LruCache源碼中具體查看,怎麼應用LinkedHashMap來實現緩存的添加,得到和刪除的。
/**
* @param maxSize for caches that do not override {@link #sizeOf}, this is
* the maximum number of entries in the cache. For all other caches,
* this is the maximum sum of the sizes of the entries in this cache.
*/
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
複製代碼
從LruCache的構造函數中能夠看到正是用了LinkedHashMap的訪問順序。
put()方法
/**
* Caches {@code value} for {@code key}. The value is moved to the head of
* the queue.
*
* @return the previous value mapped by {@code key}.
*/
public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
synchronized (this) {
putCount++;
size += safeSizeOf(key, value);
previous = map.put(key, value);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
trimToSize(maxSize);
return previous;
}
複製代碼
能夠看到put()方法並無什麼難點,重要的就是在添加過緩存對象後,調用 trimToSize()方法,來判斷緩存是否已滿,若是滿了就要刪除近期最少使用的算法。
trimToSize()方法
/**
* Remove the eldest entries until the total of remaining entries is at or
* below the requested size.
*
* @param maxSize the maximum size of the cache before returning. May be -1
* to evict even 0-sized elements.
*/
public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
if (size <= maxSize || map.isEmpty()) {
break;
}
Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
複製代碼
trimToSize()方法不斷地刪除LinkedHashMap中隊尾的元素,即近期最少訪問的,直到緩存大小小於最大值。
當調用LruCache的get()方法獲取集合中的緩存對象時,就表明訪問了一次該元素,將會更新隊列,保持整個隊列是按照訪問順序排序。這個更新過程就是在LinkedHashMap中的get()方法中完成的。
先看LruCache的get()方法
public final V get(K key) {
//key爲空拋出異常
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
//獲取對應的緩存對象
//get()方法會實現將訪問的元素更新到隊列頭部的功能
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
複製代碼
其中LinkedHashMap的get()方法以下:
public V get(Object key) {
LinkedHashMapEntry<K,V> e = (LinkedHashMapEntry<K,V>)getEntry(key);
if (e == null)
return null;
//實現排序的關鍵方法
e.recordAccess(this);
return e.value;
}
複製代碼
調用recordAccess()方法以下:
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
//判斷是不是訪問排序
if (lm.accessOrder) {
lm.modCount++;
//刪除此元素
remove();
//將此元素移動到隊列的頭部
addBefore(lm.header);
}
}
複製代碼
因而可知LruCache中維護了一個集合LinkedHashMap,該LinkedHashMap是以訪問順序排序的。當調用put()方法時,就會在結合中添加元素,並調用trimToSize()判斷緩存是否已滿,若是滿了就用LinkedHashMap的迭代器刪除隊尾元素,即近期最少訪問的元素。當調用get()方法訪問緩存對象時,就會調用LinkedHashMap的get()方法得到對應集合元素,同時會更新該元素到隊頭。
整理了最近一段時間的學習要點,作一個概括總結,後續還會再作更新。。。。