天天看點

java線程高并發,JAVA多線程高并發學習

工欲善其事,必先利其器

postman 可作為一個簡單的并發測試

JMeter相比于postman更加強大

JMeter入門: https://www.jianshu.com/p/0e4daecc8122

線程安全性:

定義:當多個線程通路某個類時,不管采用任何排程方式,不需要額外的同步或者協調,這個類都能表現出正确的結果,這個類就成為是線程安全的

線程安全性主要展現在

原子性: 互斥通路,同一時間隻能有一個線程操作

可見性: 一個線程對主記憶體的修改可以及時被其他線程觀察到

有序性: 一個線程觀察其他線程中指令執行順序,由于指令的重排序的存在,結果一般都是無序的.

原子性:

atomic包:https://www..com/chenpi/p/5375805.html

java線程高并發,JAVA多線程高并發學習

原子性: 鎖 synchronized(依賴jvm) lock(代碼實作)

java線程高并發,JAVA多線程高并發學習

可見性

不可見的原因: (1)線程交叉執行 (2)重排序結合線程交叉執行 (3)共享變量更新的值沒在工作記憶體和主記憶體間及時更新

可見性–synchronized

實作方式:JVM關于synchronized兩條規定:

(1)線程解鎖前,必須把共享變量的最新之重新整理到主記憶體中

(2)線程加鎖前,清空工作記憶體共享變量的值,使用共享變量時需從主記憶體中重新讀取最新的值

可見性-volatile (并不是線程安全的,一般不使用)

關于volatile介紹: https://www.ibm.com/developerworks/cn/java/j-jtp06197.html

實作方式: 通過加入記憶體屏障和禁止重排序優化實作

簡單來說:

寫操作後會加入store屏障指令強制将本地記憶體中的共享變量重新整理到主記憶體中

讀之前會加入load屏障指令從主記憶體中讀取共享變量:

有序性:

java線程高并發,JAVA多線程高并發學習

安全釋出對象:

釋出對象:使一個對象能被目前代碼範圍之外的代碼所使用

對象溢出:一種錯誤釋出;當一個對象還沒建構完成時,就被其他線程所見

安全釋出對象的方式:

在靜态初始化函數中初始化一個對象引用

将對象的引用儲存到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

java線程高并發,JAVA多線程高并發學習

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建立有界的通路

使用同步代碼塊而不是使用同步方法

避免使用靜态變量