高并发下Java多线程编程基础
摘要:?Java線程同步與異步 線程池 無鎖化的實現(xiàn)方案 分布鎖的實現(xiàn)方案 分享的目的: 進一步掌握多線程編程和應(yīng)用的技巧,希望對大家在平時的開發(fā)中應(yīng)對高并發(fā)編程有所幫助 Java線程同步與異步 1. 同步相關(guān)的方法有 wait, notify, notifyAll 2.
Java線程同步與異步
線程池
無鎖化的實現(xiàn)方案
分布鎖的實現(xiàn)方案
分享的目的:
進一步掌握多線程編程和應(yīng)用的技巧,希望對大家在平時的開發(fā)中應(yīng)對高并發(fā)編程有所幫助
Java線程同步與異步
1. 同步相關(guān)的方法有
wait, notify, notifyAll
2. 關(guān)鍵字
synchronized
3. JDK鎖的框架
AQS (AbstractQueuedSynchronizer)
4. AQS的實現(xiàn)類
java.util.concurrent.locks.ReentrantLock
java.util.concurrent.locks.ReentrantReadWriteLock
java.util.concurrent.CountDownLatch
java.util.concurrent.Semaphore
5. 例子——兩個線程交替打印出100以內(nèi)的奇數(shù)和偶數(shù)
主程序:
輸出結(jié)果示例:
...
...
...
思考:
讀者可以用兩種其它方法實現(xiàn),加深自己對Java線程同步和互斥的理解
用 ReentrantLock?
還是用wait和notify ?
線程池
作用:
控制線程并發(fā)數(shù)量,一般用在控制單機并發(fā)度上, 也是實現(xiàn)流控的一種方案;
實現(xiàn)原理:
1. 參數(shù)含義
corePoolSize: 核心線程的數(shù)量, 在CPU密集型和IO密集型的任務(wù)中,這個參數(shù)的設(shè)置不太一樣:
在CPU密集型的應(yīng)用中:
通常這個參數(shù)被設(shè)置為: 機器cpu核數(shù)-1, 例如機器有4個核,這個參數(shù)就被設(shè)置為3, 這樣做的即兼顧了最大的并發(fā)度,又兼顧了其它非重要的核心任務(wù)的執(zhí)行;
在IO密集的任務(wù)中:
通常這個參數(shù)被設(shè)置為機器cpu核數(shù)*(1.5 - 3),具體情況還需要根據(jù)實際業(yè)務(wù)情況進行壓測比較,然后再給出最優(yōu)的值;
maximumPoolSize: 最大核心線程的數(shù)量
poolSize: 當前線程的數(shù)量
當用戶向線程池中新提交一個線程的時候,會有如下情況:
情況1.
如果當前線程池中線程的數(shù)量小于corePoolSize, 就會創(chuàng)建一個新的線程, 并添加到線程池中;
情況2.
如果當前線程池中線程的數(shù)量等于corePoolSize, 并且等待隊列中還沒有滿,則把當前用戶添加的線程對象放在等待隊列中;
情況3.
如果當前線程池中線程的數(shù)量大于等于corePoolSize并且小于maximunPoolSize,并且等待隊列已經(jīng)滿,則創(chuàng)建一個新的線程,并添加到線程池中;
情況4.
如果當前線程池中線程的數(shù)量等于maximunPoolSize, 則會根據(jù)線程創(chuàng)建線程時候的拒絕策略,進行相應(yīng)的處理;
2. java線程對象中run方法和start方法的區(qū)別:
2.1 線程對象直接調(diào)用run方法,JVM是不會有感知,是不會直接產(chǎn)生一個新的線程, 此時程序運行的方式依然是串行的;
2.2 線程對象直接調(diào)用start方法,JVM才會有感知,會產(chǎn)生一個新的線程, 此時才會產(chǎn)生并發(fā)多線程;
線程池正是充分利用了run方法和start的區(qū)別來實現(xiàn)線程的復(fù)用;
3. 線程池的核心代碼
下面均是以jdk1.6的線程池的源碼,jdk1.7和jdk1.8線程池實現(xiàn)在上有些變化,但核心思想不變,有興趣可以自己去研究
提交線程的核心代碼:
執(zhí)行用戶任務(wù)的核心代碼:
無鎖化的實現(xiàn)方案
用線程池的方案
1. netty的reactor線程模型,參考netty官方或網(wǎng)上相關(guān)的資料
2. 異地機房數(shù)據(jù)庫之間的數(shù)據(jù)同步:
用表名+主鍵名做hash ,hash值相同的記錄被寫到同一個Kafka的Partition中去,假設(shè)一個Partition用一個線程進行消費, 這樣不同線程之間寫入目標數(shù)據(jù)庫的時候,就不會存在數(shù)據(jù)庫行鎖的競爭關(guān)系,間接實現(xiàn)了無鎖化的操作, 即線程之間并行,線程內(nèi)部串行, 如下圖所示;
用CAS的命令
1. JDK中各種類型值的原子操作
AtomicInteger
AtomicLong
AtomicBoolean
2. jdk中各種鎖的實現(xiàn), 本質(zhì)也是volitate變量+CAS
java.util.concurrent.locks.ReentrantLock
java.util.concurrent.Semaphore
java.util.concurrent.CountDownLatch
分布鎖的實現(xiàn)方案
1. tair
incr和decr操作,相當于是樂觀鎖
2. Redis/memcache
setNx命令
3. Zookeeper
充分利用watcher機制,創(chuàng)建臨時結(jié)點,誰創(chuàng)建成功,誰就獲得當前的鎖
4. 數(shù)據(jù)庫:利用數(shù)據(jù)庫的行鎖
// 加鎖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的列的唯一索引
當然具體用那種分布鎖,還需要結(jié)合業(yè)務(wù)自身的需要,一般來說,在并發(fā)量不是別大,數(shù)據(jù)庫完全可以扛得住的情況下,用數(shù)據(jù)庫實現(xiàn)分布鎖最快,最方便,而且性能的損失也非常地小;
當然現(xiàn)在很多場景下,都是分庫分表,并且加鎖和解鎖分別都只影響一行,對數(shù)據(jù)庫來說,加鎖和解鎖的 sql也是非常輕量的sql操作,因此在性能損失上不用過多的擔(dān)心;
原文鏈接
本文為云棲社區(qū)原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。
總結(jié)
以上是生活随笔為你收集整理的高并发下Java多线程编程基础的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 理论与实践:如何从Hadoop迁移到Ma
- 下一篇: 最佳实践:使用负载均衡SLB IPv6搞