高併發下Java多線程編程基礎

Java線程同步與異步
線程池
無鎖化的實現方案
分佈鎖的實現方案java

分享的目的:react

進一步掌握多線程編程和應用的技巧,但願對你們在平時的開發中應對高併發編程有所幫助sql

Java線程同步與異步

1. 同步相關的方法有數據庫

wait, notify, notifyAll編程

2. 關鍵字多線程

synchronized併發

3. JDK鎖的框架框架

AQS (AbstractQueuedSynchronizer)異步

4. AQS的實現類高併發

java.util.concurrent.locks.ReentrantLock
java.util.concurrent.locks.ReentrantReadWriteLock
java.util.concurrent.CountDownLatch
java.util.concurrent.Semaphore

5. 例子——兩個線程交替打印出100之內的奇數和偶數

主程序:

clipboard.png

clipboard.png

輸出結果示例:

clipboard.png

...
...
...

clipboard.png

思考:

讀者能夠用兩種其它方法實現,加深本身對Java線程同步和互斥的理解
用 ReentrantLock?
仍是用wait和notify ?

線程池

做用:

控制線程併發數量,通常用在控制單機併發度上, 也是實現流控的一種方案;

實現原理:

1. 參數含義

corePoolSize: 核心線程的數量, 在CPU密集型和IO密集型的任務中,這個參數的設置不太同樣:

在CPU密集型的應用中:

一般這個參數被設置爲: 機器cpu核數-1, 例如機器有4個核,這個參數就被設置爲3, 這樣作的即兼顧了最大的併發度,又兼顧了其它非重要的核心任務的執行;

在IO密集的任務中:

一般這個參數被設置爲機器cpu核數*(1.5 - 3),具體狀況還須要根據實際業務狀況進行壓測比較,而後再給出最優的值;

maximumPoolSize: 最大核心線程的數量
poolSize: 當前線程的數量

當用戶向線程池中新提交一個線程的時候,會有以下狀況:

狀況1.

若是當前線程池中線程的數量小於corePoolSize, 就會建立一個新的線程, 並添加到線程池中;

狀況2.

若是當前線程池中線程的數量等於corePoolSize, 而且等待隊列中尚未滿,則把當前用戶添加的線程對象放在等待隊列中;

狀況3.

若是當前線程池中線程的數量大於等於corePoolSize而且小於maximunPoolSize,而且等待隊列已經滿,則建立一個新的線程,並添加到線程池中;

狀況4.

若是當前線程池中線程的數量等於maximunPoolSize, 則會根據線程建立線程時候的拒絕策略,進行相應的處理;

2. java線程對象中run方法和start方法的區別:

2.1 線程對象直接調用run方法,JVM是不會有感知,是不會直接產生一個新的線程, 此時程序運行的方式依然是串行的;

2.2 線程對象直接調用start方法,JVM纔會有感知,會產生一個新的線程, 此時纔會產生併發多線程;
線程池正是充分利用了run方法和start的區別來實現線程的複用;

3. 線程池的核心代碼

下面均是以jdk1.6的線程池的源碼,jdk1.7和jdk1.8線程池實如今上有些變化,但核心思想不變,有興趣能夠本身去研究

提交線程的核心代碼:

clipboard.png

執行用戶任務的核心代碼:

clipboard.png

無鎖化的實現方案

用線程池的方案

1. netty的reactor線程模型,參考netty官方或網上相關的資料

2. 異地機房數據庫之間的數據同步:

用表名+主鍵名作hash ,hash值相同的記錄被寫到同一個Kafka的Partition中去,假設一個Partition用一個線程進行消費, 這樣不一樣線程之間寫入目標數據庫的時候,就不會存在數據庫行鎖的競爭關係,間接實現了無鎖化的操做, 即線程之間並行,線程內部串行, 以下圖所示;

clipboard.png

用CAS的命令

1. JDK中各類類型值的原子操做

AtomicInteger
AtomicLong
AtomicBoolean

2. jdk中各類鎖的實現, 本質也是volitate變量+CAS

java.util.concurrent.locks.ReentrantLock
java.util.concurrent.Semaphore
java.util.concurrent.CountDownLatch

分佈鎖的實現方案

1. tair

incr和decr操做,至關因而樂觀鎖

2. Redis/memcache

setNx命令

3. Zookeeper

充分利用watcher機制,建立臨時結點,誰建立成功,誰就得到當前的鎖

4. 數據庫:利用數據庫的行鎖

// 加鎖SQL
update trade_base set status = 1 where trade_no=「XXX」 and status = 0;
// 解鎖SQL
update trade_base set status = 0 where trade_no=「XXX」 and status = 1;
注意trade_base表上一要有trade_no的列的惟一索引

固然具體用那種分佈鎖,還須要結合業務自身的須要,通常來講,在併發量不是別大,數據庫徹底能夠扛得住的狀況下,用數據庫實現分佈鎖最快,最方便,並且性能的損失也很是地小;

固然如今不少場景下,都是分庫分表,而且加鎖和解鎖分別都隻影響一行,對數據庫來講,加鎖和解鎖的 sql也是很是輕量的sql操做,所以在性能損失上不用過多的擔憂。

本文做者:友德

閱讀原文

本文爲雲棲社區原創內容,未經容許不得轉載。

相關文章
相關標籤/搜索