工欲善其事,必先利其器
postman 可作為一個簡單的并發測試
JMeter相比于postman更加強大
JMeter入門: https://www.jianshu.com/p/0e4daecc8122
線程安全性:
定義:當多個線程通路某個類時,不管采用任何排程方式,不需要額外的同步或者協調,這個類都能表現出正确的結果,這個類就成為是線程安全的
線程安全性主要展現在
原子性: 互斥通路,同一時間隻能有一個線程操作
可見性: 一個線程對主記憶體的修改可以及時被其他線程觀察到
有序性: 一個線程觀察其他線程中指令執行順序,由于指令的重排序的存在,結果一般都是無序的.
原子性:
atomic包:https://www..com/chenpi/p/5375805.html

原子性: 鎖 synchronized(依賴jvm) lock(代碼實作)
可見性
不可見的原因: (1)線程交叉執行 (2)重排序結合線程交叉執行 (3)共享變量更新的值沒在工作記憶體和主記憶體間及時更新
可見性–synchronized
實作方式:JVM關于synchronized兩條規定:
(1)線程解鎖前,必須把共享變量的最新之重新整理到主記憶體中
(2)線程加鎖前,清空工作記憶體共享變量的值,使用共享變量時需從主記憶體中重新讀取最新的值
可見性-volatile (并不是線程安全的,一般不使用)
關于volatile介紹: https://www.ibm.com/developerworks/cn/java/j-jtp06197.html
實作方式: 通過加入記憶體屏障和禁止重排序優化實作
簡單來說:
寫操作後會加入store屏障指令強制将本地記憶體中的共享變量重新整理到主記憶體中
讀之前會加入load屏障指令從主記憶體中讀取共享變量:
有序性:
安全釋出對象:
釋出對象:使一個對象能被目前代碼範圍之外的代碼所使用
對象溢出:一種錯誤釋出;當一個對象還沒建構完成時,就被其他線程所見
安全釋出對象的方式:
在靜态初始化函數中初始化一個對象引用
将對象的引用儲存到volatile類型或者AtomicReference對象中
将對象的引用儲存到某個正确構造對象的final類型域中
将對象的引用儲存到一個有鎖保護的域中
知識點
普通的懶漢式單列模式并不是線程安全的 **(靜态域,和靜态代碼塊的順序執行)**
處理為線程安全的方式
(1)在靜态工廠方法中添加synchronized關鍵之(不推薦,性能開銷大)
(2) volatile+雙重檢測機制
private volatile static SingletonExample5 instance = null;
// 靜态的工廠方法
public static SingletonExample5 getInstance() {
if (instance == null) { // 雙重檢測機制
synchronized (SingletonExample.class) { // 同步鎖
if (instance == null) {
instance = new SingletonExample5();
}
}
}
return instance;
}
餓漢式單利模式是線程安全的(但初始化時可能開銷比較大)
使用枚舉建立餓漢式單例也是安全的(推薦)
public class SingletonExample7 {
// 私有構造函數
private SingletonExample7() {
}
public static SingletonExample7 getInstance() {
return Singleton.INSTANCE.getInstance();
}
private enum Singleton {
INSTANCE;
private SingletonExample7 singleton;
// JVM保證這個方法絕對隻調用一次
Singleton() {
singleton = new SingletonExample7();
}
public SingletonExample7 getInstance() {
return singleton;
}
}
}
不可變對象: (隻要釋出了就是安全的)
不可變對象需要滿足的條件:
(1)對象建立後器狀态不能修改
(2)對象所有域都是final類型
(3)對象都是正确建立的(在對象建立中,this引用沒有逸出)
final :
修飾類:不能被繼承
修飾方法: 不被繼承類覆寫
修飾變量: 基本類型變量 指派一次後不能被修改.引用類型變量指派後不能再指向其他對象
Collections.unmodifiableXX 處理的Collection List Set Map 都不允許改變
ImmutableXXX 處理的Collection List Set Map 都不允許改變
Map map = new HashMap<>();
map.put(1, 2);
map.put(3, 4);
map.put(5, 6);
map = Collections.unmodifiableMap(map); //這裡處理之後
map.put(1, 3); //運作這裡會報錯
System.out.println(map.get(1));
線程封閉 (把對象封裝到一個線程中,即使對象不是線程安全的這不會出現線程安全,)
Ad_hoc線程封閉:程式實作,最糟糕,忽略
堆棧封閉: 局部變量 無并發問題
ThreadLocal線程封閉:相當好的封閉方法(例如資料庫jdbc連接配接)
(ThreadLocal内部維護了一個Map Map的Key是線程的名稱 Map的值就是要封閉的對象)
線程不安全的類
StringBuilder 不安全; (StringBuffer)
SimpleDateFormat 不安全 (安全類 JodaTime)
ArrayList HashSet HashMap 等Collections (java中線程安全的集合有Vector和HashTable)
線程安全----同步容器(效率低,還不確定安全,容器周遊時進行增删容易出問題)
1\本身就安全的類 Vector Stack HashTable( k v 不能為null) (都是使用synchronized)
2\Collections.synchronizedXXX(List,Set,Map)
線程安全----并發容器(JUC)(JDK1.5之後引入的并發包)
CopyOnWriteArrayList(ArrayList):(寫操作時複制,寫完後再刷到原有數組中,讀寫分離)
缺點1:寫操作時需要拷貝,會消耗記憶體
缺點2:不能用于實時讀的場景,更适合讀多寫少的場景
CopyOnWriteArraySet(HashSet):
ConcurrentSkipListSet(TreeSet):
ConcurrentHashMap(HashMap): 不允許null值
ConcurrentSkipListMap(TreeMap):實作原理SkipList(Key有序)(在多線程中應使用可以更好的提高并發度)
安全共享對象政策:
1\線程限制:一個被線程限制的對象,由線程獨占,并且隻能被占有他的線程修改
2\共享隻讀:一個共享隻讀對象,在沒有額外同步的情況下可以被多個線程并發通路,但不允許任何線程修改
3\線程安全對象:線程安全的對象或者容器,在内部通過同步機制保證安全,其他線程無需通過額外的同步就可以去通路它
4\被守護對象:被守護對象隻能通過擷取特定的鎖來通路
線程安全----并發容器(JUC)----同步器(AQS)
AbstractQueuedSynchronizer - JUC的核心
底層實作方式,雙向連結清單
AQS設計
1\使用Node實作FIFO隊列,可以用于建構鎖或者其他同步裝置的基礎架構
2\利用一個int類型表示狀态
3\ 使用方法時繼承
4\子類通過繼承并通過實作它的方法管理其狀态{acquire和release}的方法操作狀态
5\可以同時實作排他鎖和共享鎖(獨占 共享)
AQS 同步元件有
CountDownLatch Semaphore CyclicBarrier ReenTrantLock Condition FutureTask
CountDownLatch:同步輔助類
通過它可以完成類似阻塞目前線程的功能 即 一個線程或者多個線程一直等待,直到其他線程執行的操作完成
有一個給定的計數器進行初始化(該計數器時原子操作),調用該類await()方法,會讓線程一直處于阻塞狀态.直到其他線程調用countDown()将目前計數器的值變為0,這種操作隻會出現一次,計數器是不能被重置的
private final static int threadCount = 200;
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
try {
test(threadNum);
} catch (Exception e) {
log.error("exception", e);
} finally {
countDownLatch.countDown(); //會讓計數器中threadCount -1
}
});
}
countDownLatch.await();//目前線程會被挂起,知道計數器中的數值為0才會被喚醒
// countDownLatch.await(10, TimeUnit.MILLISECONDS);//等待指定時間,到期自動喚醒
log.info("finish");
exec.shutdown(); //關閉線程池 目前已有的線程執行完
}
private static void test(int threadNum) throws Exception {
Thread.sleep(100); log.info("{}", threadNum); Thread.sleep(100);
}
Semaphore(信号量 ):可以控制同一時間通路某個資源的個數
提供兩個核心方法acquire(擷取許可,沒有就等待)和release(操作完成後釋放許可)
Semaphore相關方法
void acquire() //擷取一個許可
void acquire(int permits) //擷取permits個許可
void release() //釋放一個許可
void release(int permits) //釋放permits個許可
boolean tryAcquire() ; //嘗試擷取一個許可,若擷取成功,則立即傳回true,若擷取失敗,則立即傳回false
boolean tryAcquire(long timeout, TimeUnit unit) ; //嘗試擷取一個許可,若在指定的時間内擷取成功,則立即傳回true,否則則立即傳回false
boolean tryAcquire(int permits); //嘗試擷取permits個許可,若擷取成功,則立即傳回true,若擷取失敗,則立即傳回false
boolean tryAcquire(int permits, long timeout, TimeUnit unit) ; //嘗試擷取permits個許可,若在指定的時間内擷取成功,則立即傳回true,否則則立即傳回false
private final static int threadCount = 20; //線程數
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(3); //幾個許可
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
try {
semaphore.acquire(); // 擷取一個許可
//semaphore.acquire(3); // 擷取多個許可
test(threadNum);
semaphore.release(); // 釋放一個許可
} catch (Exception e) {
log.error("exception", e);
}
});
}
exec.shutdown();
}
private static void test(int threadNum) throws Exception {
log.info("{}", threadNum);
Thread.sleep(1000);
}
CyclicBarrier: 回環栅欄 (同步輔助類) 也是可以控制同一時間通路某個資源的個數
可以完成多個線程之間互相等待(與CountDownLatch差別),隻有當每個線程都準備就緒後才能往下執行 也是使用計數器實作(計數器初始值為零.計數器可以重複使用 與CountDownLatch差別)
通過它可以實作讓一組線程等待至某個狀态之後再全部同時執行。叫做回環是因為當所有等待線程都被釋放以後,CyclicBarrier可以被重用。我們暫且把這個狀态就叫做barrier,當調用await()方法之後,線程就處于barrier了。
CyclicBarrier提供2個構造器:
CyclicBarrier(int parties, Runnable barrierAction)
public CyclicBarrier(int parties)
參數parties指讓多少個線程或者任務等待至barrier狀态;參數barrierAction為當這些線程都達到barrier狀态時會執行的内容。
CyclicBarrier中最重要的方法就是await方法
用來挂起目前線程,直至所有線程都到達barrier狀态再同時執行後續任務;
await(long timeout, TimeUnit unit)方法:
讓這些線程等待至一定的時間,如果還有線程沒有到達barrier狀态就直接讓到達barrier的線程執行後續任務。
private static CyclicBarrier barrier = new CyclicBarrier(5, () -> {
log.info("callback is running");
});
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int threadNum = i;
Thread.sleep(1000);
executor.execute(() -> {
try {
race(threadNum);
} catch (Exception e) {
log.error("exception", e);
}
});
}
executor.shutdown();
}
private static void race(int threadNum) throws Exception {
Thread.sleep(1000);
log.info("{} is ready", threadNum);
barrier.await(); //計數器加1 //直到計數器值為5時下面的才會執行
log.info("{} continue", threadNum); //同時執行了
}
ReenTrantLock:
可重入鎖:線程擷取一次數,計數器加1,釋放一次,計數器減一 計數器為零時鎖才被釋放
鎖的實作:JDK實作的 (synchronized是JVM實作的)
性能:synchronized優化後官方推薦 synchronized
功能: synchronized使用便捷,可以自動釋放 ReenTrantLock需要手動釋放 ReenTrantLock更靈活
ReenTrantLock獨有功能:
(1)可指定是公平鎖還是非公平鎖
(2)提供了一個Condition類可以分組喚醒需要的線程(synchronized随機喚醒,或者全部喚醒)
(3)提供能夠中斷等待鎖的線程機制,
StampedLock它是java8在java.util.concurrent.locks新增的一個API。 http://www.importnew.com/14941.html
StampedLock控制鎖有三種模式(寫,讀,樂觀讀),一個StampedLock狀态是由版本和模式兩個部分組成,鎖擷取方法傳回一個數字作為票據stamp,它用相應的鎖狀态表示并控制通路,數字0表示沒有寫鎖被授權通路。在讀鎖上分為悲觀鎖和樂觀鎖。速度比ReentrantReadWriteLock要快很多
總結
(1) synchronized是在JVM層面上實作的,不但可以通過一些監控工具監控synchronized的鎖定,而且在代碼執行時出現異常,JVM會自動釋放鎖定;
(2) ReentrantLock、ReentrantReadWriteLock,、StampedLock都是對象層面的鎖定,要保證鎖定一定會被釋放,就必須将unLock()放到finally{}中;
(3)StampedLock 對吞吐量有巨大的改進,特别是在讀線程越來越多的場景下;
(4)StampedLock有一個複雜的API,對于加鎖操作,很容易誤用其他方法;
(5)當隻有少量競争者的時候,synchronized是一個很好的通用的鎖實作;
(6)當線程增長能夠預估,ReentrantLock是一個很好的通用的鎖實作;
Condition: https://www..com/skywang12345/p/3496716.html
Java并發程式設計:Callable、Future和FutureTask https://www..com/dolphin0520/p/3949310.html
線程池:
線程池優點:
重用存在的線程,減少對象建立消亡的開銷
可有效控制最大并發線程數,提高系統資源使用率,同時可避免過多資源競争,避免阻塞
提供定時執行,定期執行,單線程,并發數控制等功能
ThreadPoolExecutor學習
coprePoolSize:核心線程數量 (有線程就放在裡面執行,即便有線程是空閑的,也建立新的線程)
maximumPoolSize:最大線程數 (當workQueue滿了才會建立新的線程執行)
workQueue:阻塞隊列,存儲等待執行的任務,線程池滿的時候未執行的線程會放在workQueue中
keepAliveTime:線程沒有任務執行時最多保持多久時間終止(核心線程中的線程空閑時間)
threadFactory:線程工廠,用來建立線程
rejectHandler:拒絕政策 workQueue滿了.線程池滿了,再有新線程送出(有四種政策1,直接抛出異常(預設);2,用調用者所在的線程執行任務;3,丢棄阻塞隊列中靠最前的任務;4,直接丢棄)
線程池狀态: https://blog..net/L_kanglin/article/details/57411851
RUNNING: 線程池處在RUNNING狀态時,能夠接收新任務,以及對已添加的任務進行處理。
SHUTDOWN: 線程池處在SHUTDOWN狀态時,不接收新任務,但能處理已添加的任務。
STOP: 線程池處在STOP狀态時,不接收新任務,不處理已添加的任務,并且會中斷正在處理的任務。
TIDYING: 當所有的任務已終止,線程任務數量為0,線程池會變為TIDYING狀态.當線程池在STOP狀态下,線程池中執行的任務為空時,就會由STOP -> TIDYING。
TERMINATED: 線程池徹底終止,就變成TERMINATED狀态。
線程狀态分為 ; https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr034.html
NEW 該線程尚未啟動
RUNNABLE 線程正在JVM中執行。
BLOCKED 線程被阻塞等待螢幕鎖定
WAITING 線程無限期地等待另一個線程執行特定操作。
TIMED_WAITING 線程正在等待另一個線程執行最多指定等待時間的操作。
TERMIMNATED 線程已經退出。
線程池方法:
execute():送出任務,交給線程池執行
submit() : 送出任務能傳回執行結果 execute+Future
shutdown():關閉線程池,等任務執行完
shutdownNow() :關閉線程池,不等任務執行完
getTaskCount() 線程池執行和未執行任務總數
getCompletedTaskCount(): 已完成任務數量
getPoolSize() :線程池目前線程數量
9 ) getActiveCount() 目前線程池中正在執行任務的線程數量
多線程高并發最佳實踐:
使用本地變量
使用不可變類
最小化鎖的作用域範圍
使用線程池的executor 而不是直接new Thread
甯願使用同步,也不要使用線程的wait和notify
使用BlockingQueue實作生産消費
使用并發集合而不是加了鎖的同步集合
使用semaphore建立有界的通路
使用同步代碼塊而不是使用同步方法
避免使用靜态變量