并發編程2
- synchronized鎖實現
- **AQS**
- **ReentrantLock實現**
- **JUC 常用類**
- 池的概念
- ThreadLocal
- ThreadLocal原理
- 內存泄露
- 強引用:
- 軟引用
- 弱引用
- 虛引用
- ThreadLocal內存泄露
synchronized鎖實現
synchronized是一個關鍵字,實現同步,還需要我們提供一個同步鎖對象,記錄鎖狀態,記錄線程信息
控制同步,是依靠底層的指令實現的.
如果是同步方法,在指令中會為方法添加ACC_SYNCHRONIZED標志
如果是同步代碼塊,在進入到同步代碼塊時,會執行monitorenter, 離開同步代碼塊時或者出異常時,執行monitorexit
AQS
AQS(AbstractQueuedSynchronizer 抽象同步隊列) 是一個實現線程同步的框架
并發包中很多類的底層都用到了AQS
class AbstractQueuedSynchronizer {private transient volatile Node head;private transient volatile Node tail;private volatile int state; //表示有沒有線程訪問共享數據 默認是0 表示沒有線程訪問//修改狀態的方法protected final boolean compareAndSetState(int expect, int update) {return unsafe.compareAndSwapInt(this, stateOffset, expect, update);}static final class Node {volatile Node prev;volatile Node next;volatile Thread thread;}}
ReentrantLock實現
ReentrantLock完全同過java代碼控制
class ReentrantLock{abstract static class Sync extends AbstractQueuedSynchronizer {abstract void lock();}//非公平鎖static final class NonfairSync extends Sync {void lock(){}}//公平鎖static final class FairSync extends Sync {void lock(){}}}
JUC 常用類
在集合類中,像Vector,Hashtable這些類加鎖時都是直接把鎖加載方法上了,性能就低, 在并發訪問量小的情況下,還可以使用, 大并發訪問量下,性能就太低了.
ConcurrentHashMap
HashMap適合單線程場景下的,不允許多個線程同時訪問操作,如果有多線程訪問會報異常Hashtable 是線程安全的 直接給方法加鎖,效率低ConcurrentHashMap 是線程安全的,沒有直接給方法加鎖, 用哈希表中每一個位置上的第一個元素(第一個是存在元素)作為鎖對象哈希表長度是16,那么就有16把鎖對象,鎖住自己的位置即可,這樣如果多個線程如果操作不同的位置,那么相互不影響,只有多個線程操作同一個位置時,才會等待如果位置上沒有任何元素,那么采用cas機制插入數據到對應的位置Hashtable ,ConcurrentHashMap 鍵值都不能為null為什么這樣設計,鍵值都不能為null?map.put("b","b")為了消除歧義 System.out.println(map1.get("a"));//null 值是null 還是鍵不存在返回null
CopyOnWriteArrayList
ArrayList 是單線程場景下使用的,在多線程場景下會報異常
Vector 是線程安全的,在方法上加了鎖,效率低
CopyOnWriteArrayList 寫方法操作加了鎖(ReentrantLock實現的),
在寫入數據時,先把原數組做了備份,把要添加的數據寫入到備份數組中,當寫入完成后,再把修改的數組賦值到原數組中去
給寫加了鎖,讀沒有加鎖,讀的效率變高了, 這種適合寫操作少,讀操作多的場景
CopyOnWriteArraySet
CopyOnWriteArraySet 的實現基于 CopyOnWriteArrayList,不能存儲重復數據。
輔助類 CountDownLatch
池的概念
字符串常量池
String s1 = “abc”; String s2=“abc”; s1==s2//true
Integer自動裝箱 緩存了-128 --+127之間的對象
Integer a = 100; Integer b = 100; a==b //true IntegerCache.cache[i + (-IntegerCache.low)];
數據庫連接池
阿里巴巴Druid數據庫連接池
幫我們緩存一定數量的鏈接對象,放在池子里,用完還回到池子中,
減少了對象的頻繁創建和銷毀的時間開銷
線程池
為減少頻繁的創建和銷毀線程,
jdk5開始引入了線程池,
建議使用ThreadPoolExecutor類來創建線程池, 這樣提高效率.
Java.uitl.concurrent.ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
7個參數
corePoolSize: 核心線程池中的數量(初始化的數量) 5
maximumPoolSize:線程池中最大的數量 10 5
keepAliveTime: 空閑線程存活時間 當核心線程池中的線程足以應付任務時, 非核心線程池中的線程在指定空閑時間到期后,會銷毀.
unit: 時間單位
workQueue: 5 等待隊列, 當核心線程池中的線程都在使用時,如果有任務繼續到來,會先將等待的任務放到隊列中,如果隊列也滿了,才會創建新的線程(非核心線程池中的線程)
threadFactory:線程工廠,用來創建線程池中的線程
handler:拒絕處理任務時的策略 4種拒絕策略
線程池工作流程
當有大量的任務到來時,先判斷核心線程池中的線程是否都忙著,
? 有空閑的,直接讓核心線程中的線程執行任務
? 沒有空閑的, 判斷等待隊列是否已滿,
? 如果沒滿,把任務添加到隊列等待
? 如果已滿,判斷非核心線程池中的線程是否都忙著
? 如果有空閑的,沒滿,交由非核心線程池中的線程執行
? 如果非核心線程池野已經滿了,那么就使用對應的拒絕策略處理.
4種拒絕策略:
AbortPolicy: 拋異常
CallerRunsPolicy: 由提交任務的線程執行 例如在main線程提交,則由main線程執行拒絕的任務 DiscardOldestPolicy: 丟棄等待時間最長的任務
DiscardPolicy: 丟棄最后不能執行的任務
提交任務的方法
void execute(任務); 提交任務沒有返回值Future<?> submit = executor.submit(myTask);//提交任務可以接收返回值
submit.get();
關閉線程池
shutdown(); 執行shutdown()后,不再接收新的任務,會把線程池中還有等待隊列中已有的任務執行完,再停止
shutdownNow(); 立即停止,隊列中等待的任務就不再執行了.
如果有空閑的,沒滿,交由非核心線程池中的線程執行
? 如果非核心線程池野已經滿了,那么就使用對應的拒絕策略處理.
4種拒絕策略:
AbortPolicy: 拋異常
CallerRunsPolicy: 由提交任務的線程執行 例如在main線程提交,則由main線程執行拒絕的任務 DiscardOldestPolicy: 丟棄等待時間最長的任務
DiscardPolicy: 丟棄最后不能執行的任務
提交任務的方法
void execute(任務); 提交任務沒有返回值Future<?> submit = executor.submit(myTask);//提交任務可以接收返回值
submit.get();
關閉線程池
shutdown(); 執行shutdown()后,不再接收新的任務,會把線程池中還有等待隊列中已有的任務執行完,再停止
shutdownNow(); 立即停止,隊列中等待的任務就不再執行了.
ThreadLocal
ThreadLocal中填充的變量屬于當前線程,改變量對其他線程而言是隔離的
ThreadLocak為變量在每個線程創建了一個副本,每個線程可以訪問自己內部的副本變量
static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
@Overrideprotected Integer initialValue() {return 1;}
};
ThreadLocal原理
首 先 ThreadLocal 是 一 個 泛 型 類 , 保 證 可 以 接 受 任 何 類 型 的 對 象 ,ThreadLocal 內 部 維 護 了 一 個 Map , ThreadLocal 實 現 了 一 個 叫做 ThreadLocalMap 的靜態內部類。
而我們使用的 get()、set() 方法其實都是由這個 ThreadLocalMap 類對應的 get()、set() 方法實現的。首 先 ThreadLocal 是 一 個 泛 型 類 , 保 證 可 以 接 受 任 何 類 型 的 對 象 。
最終變量是放在當前線程的ThreadLocalMap中,并不是存在ThreadLocal上,ThreadLocal主要作為key,用于存讀操作
內存泄露
當對象已經不再被引用,但是垃圾回收機制無法回收該對象,就會產生內存泄露問題(例如數據庫鏈接對象,流對象,socker)
強引用:
當內存不足,JVM開始垃圾回收,對于強引用的對象,就算是出現了OOM也不會對該對象進行回收,死都不收。
強引用是我們最常見的普遍對象引用,只要還有強引用指向一個對象,就能表明對象還活著,垃圾收集器不會碰這種對象。在JAVA最常見的就是強引用,把一個對象賦給一個引用變量就是一個強引用。當一個對象被強引用變量引用時,它處于可達狀態,它是不可能被垃圾回收機制回收的,即使該對象以后永遠都不會被用到JVM也不會回收,因此強引用時造成Java內存泄漏的主要原因之一。
對于一個普通的對象,如果沒有其他的引用關系,只要超過了引用的作用域或者顯式地將相應(強)引用復制為null,一版認為就是可以背垃圾收集了。
public class StrongReferenceDemo {public static void main(String []args) {Object obj1 = new Object(); //這樣定義默認是強引用Object obj2 = obj1;obj1 = null;System.gc();System.out.println(obj1);System.out.println(obj2);}
}
對象如果有強引用關系,必定不會被回收
軟引用
軟引用是一種相對強引用弱化了一些的引用,需要用java.lang.ref.SoftReference類來實現,可以讓對象豁免一些垃圾收集,對于只有軟引用的對象來說,
當系統內存充足時它不會被回收,當系統內存不足時它會被回收。
軟引用通常用在對內存敏感的程序中,比如高速緩存就有用到軟引用,內存夠用的時候就保留,不夠用就回收!
public class SoftReferenceDemo {public static void main(String []args) {Object o1 = new Object();SoftReference<Object> softReference = new SoftReference<Object>(o1);System.out.println(o1);System.out.println(softReference.get());o1 = null;System.gc();try {byte[] bytes = new byte[30*1024*1024];} finally {System.out.println(o1);System.out.println(softReference.get());}}
}
弱引用
弱引用需要用java.lang.ref.WeakReference類來實現,它比軟引用的生存區更短。
對于只有弱引用的對象來說,只要垃圾回收機制一運行,不管JVM的內存空間是否足夠,都會回收該對象占用的內存。
public class WeakReferenceDemo {public static void main(String[] args) {Object o1 = new Object();WeakReference<Object> weakReference = new WeakReference<Object>(o1);System.out.println(o1);System.out.println(weakReference.get());o1 = null;System.gc();System.out.println("----------------------------");System.out.println(o1);System.out.println(weakReference.get());}
}
虛引用
虛引用需要java.lang.ref.PhantomReference類來實現。
顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用并不會決定對象的生命周期。
如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時間都可能被垃圾回收
器回收,它不能單點使用也不能通過它訪問對象,虛引用必須和引用隊列(ReferenceQueue)聯合使用。
虛引用的主要作用是跟蹤對象被垃圾回收的狀態,僅僅是提供了一種確保對象被finalize以后,做某些事情的機制。PhantomReference的get方法總是返回null,因此無法訪問對應的引用對象,其意義在于說明一個對象已經進入了finalization階段,可以被gc回收,用來實現比finalization機制更靈活的回收操作。
換句話說,設置虛引用關聯的唯一目的,就是在這個對象被收集器回收的時候收到一個系統通知或者后續添加進一步的處理。Java技術允許使用fianlize()方法在垃圾收集器將對象從內存中清除出去之前做必要的清理工作
public class PhantomReferenceDemo {public static void main(String []args) throws Exception {Object o1 = new Object();ReferenceQueue<Override> referenceQueue = new ReferenceQueue<>();PhantomReference<Object> phantomReference = new PhantomReference(o1,referenceQueue);System.out.println(o1);System.out.println(phantomReference.get());System.out.println(referenceQueue.poll());System.out.println("-----------------");o1 = null;System.gc();Thread.sleep(500);System.out.println(o1);System.out.println(phantomReference.get());System.out.println(referenceQueue.poll());}
}
ThreadLocal內存泄露
TreadLocalMap 使用 ThreadLocal 的弱引用作為 key,如果一個 ThreadLocal不存在外部強引用時,Key(ThreadLocal)勢必會被 GC 回收,這樣就會導致ThreadLocalMap 中 key 為 null, 而 value 還存在著強引用,只有 thead 線程退出以后,value 的強引用鏈條才會斷掉。
但如果當前線程再遲遲不結束的話,這些 key 為 null 的 Entry 的 value 就會一直存在一條強引用鏈:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永遠無法回收,造成內存泄漏。
ThreadLocal 正確的使用方法
每次使用完 ThreadLocal 都調用它的 remove()方法清除數據。