一浪蹂、序言
本文講述僅針對(duì) JVM 層次的內(nèi)置鎖,不涉及分布式鎖告材。
鎖有多種分類形式坤次,比如公平鎖與非公平鎖、可重入鎖與非重入鎖斥赋、獨(dú)享鎖與共享鎖缰猴、樂(lè)觀鎖與悲觀鎖、互斥鎖與讀寫(xiě)鎖灿渴、自旋鎖洛波、分段鎖和偏向鎖/輕量級(jí)鎖/重量級(jí)鎖。
下面將配合示例講解各種鎖的概念骚露,期望能夠達(dá)到如下目標(biāo):一是在生產(chǎn)環(huán)境中不錯(cuò)誤的使用鎖蹬挤;二是在生產(chǎn)環(huán)境中選擇恰當(dāng)?shù)逆i。
對(duì)鎖了解不多的情況下棘幸,應(yīng)該首先保證業(yè)務(wù)的正確性焰扳,然后考慮性能,比如萬(wàn)金油synchronized
鎖或者自帶多重屬性的ReentrantReadWriteLock
鎖误续。不因并發(fā)導(dǎo)致業(yè)務(wù)錯(cuò)誤吨悍,不出現(xiàn)死鎖。
隨著對(duì)鎖的了解增多蹋嵌,需要更加精準(zhǔn)的選擇各類鎖以保證更高性能要求育瓜。
二、鎖的分類
Java 中有兩種加鎖的方式:一是 synchronized 關(guān)鍵字栽烂,二是用 Lock 接口的實(shí)現(xiàn)類躏仇。
需要通過(guò)加(互斥)鎖來(lái)解決線程安全問(wèn)題的鎖稱之為悲觀鎖;不通過(guò)加鎖來(lái)解決線程安全問(wèn)題的鎖稱之為樂(lè)觀鎖腺办。
鎖的性能比較:互斥鎖 < 讀寫(xiě)鎖焰手、自旋鎖 < 樂(lè)觀鎖
。
讀寫(xiě)鎖和自旋鎖分別從兩個(gè)不同角度提升鎖的效率怀喉,前者通過(guò)共享讀鎖來(lái)提高效率书妻;后者通過(guò)回避阻塞-喚醒
上下文切換來(lái)提高效率。
(一)公平鎖/非公平鎖
公平鎖和非公平鎖具體實(shí)現(xiàn)類有Semaphore躬拢、ReentrantLock和ReentrantReadWriteLock躲履。
公平與否是指參與競(jìng)爭(zhēng)的線程是否都有機(jī)會(huì)獲得鎖见间,公平鎖:多個(gè)線程按照申請(qǐng)鎖的順序來(lái)獲取鎖;非公平鎖并不是按照申請(qǐng)鎖的順序來(lái)獲取鎖崇呵,極端情況下可能會(huì)有線程一直無(wú)法獲取到鎖缤剧。
公平鎖維護(hù)一個(gè)虛擬的先進(jìn)先出隊(duì)列,按照次序排隊(duì)申請(qǐng)獲取鎖域慷。
1荒辕、概念解讀
為何按鎖的申請(qǐng)順序按照先進(jìn)先出的順序獲取鎖能夠保證公平?當(dāng)采用先進(jìn)先出的排隊(duì)機(jī)制時(shí)犹褒,所有處于等待隊(duì)列中的線程理論上都有機(jī)會(huì)獲得鎖抵窒,并且隨著時(shí)間的推移,獲得鎖的機(jī)會(huì)越來(lái)越大叠骑。
不是按照申請(qǐng)鎖的順序來(lái)獲取鎖如何解讀李皇?synchronized 鎖是典型的非公平鎖,表現(xiàn)形式是所有參與獲取鎖的線程是否能夠獲得鎖是不可預(yù)測(cè)的宙枷。
公平鎖的深層次內(nèi)涵是只要線程有獲得鎖的需求掉房,在絕對(duì)的時(shí)間里,一定能夠獲得鎖慰丛。比如服務(wù)器連接資源卓囚,不存在客戶端連接不上的情況,這是公平鎖的典型的應(yīng)用诅病。
2哪亿、鎖代碼層次表示
Semaphore
// 非公平鎖
Semaphore unfairLock = new Semaphore(5);
// 公平鎖
Semaphore fairLock = new Semaphore(5,true);
ReentrantLock
// 非公平鎖
ReentrantLock unfairLock = new ReentrantLock();
// 公平鎖
ReentrantLock fairLock = new ReentrantLock(true);
ReentrantReadWriteLock
// 非公平鎖
ReentrantReadWriteLock unfairLock = new ReentrantReadWriteLock();
// 公平可鎖
ReentrantReadWriteLock fairLock = new ReentrantReadWriteLock(true);
上面提到的 3 個(gè)鎖的實(shí)現(xiàn)類能配置公平鎖或者非公平鎖,真正實(shí)現(xiàn)鎖的公平與否是由AbstractQueuedSynchronizer
抽象類的子類定義的贤笆。
3蝇棉、優(yōu)劣對(duì)比
鎖 | 獲取鎖事件 | 鎖的效率 | 備注 |
---|---|---|---|
公平鎖 | 可以樂(lè)觀估計(jì) | 相對(duì)較低 | |
非公平鎖 | 饑餓狀態(tài) | 相對(duì)較高 | 如果對(duì)鎖沒(méi)有特別的要求,優(yōu)先選用非公平鎖 |
公平鎖的效率比非公平鎖低的原因如下:
- 所有想獲取鎖的線程必須先到先進(jìn)先出隊(duì)列注冊(cè)芥永,排隊(duì)才能獲取鎖篡殷,從獲取鎖的流程上增加額外的操作;
- 有隊(duì)列必然涉及線程的阻塞與喚醒操作埋涧,增加了操作系統(tǒng)層次上下文切換調(diào)度開(kāi)銷贴唇。
(二)可重入鎖/非可重入鎖
可重入鎖是指某個(gè)線程獲得特定鎖后,同一個(gè)線程內(nèi)可以多次獲得該鎖飞袋。synchronized
關(guān)鍵字、ReentrantLock
和ReentrantReadWriteLock
屬于可重入鎖链患,Jdk 內(nèi)置除此之外其它的鎖都是不可重入鎖巧鸭。
可重入鎖有兩個(gè)重要的特性:同一個(gè)線程、重復(fù)獲取鎖麻捻。
1纲仍、可重入鎖必要性分析
可重入鎖能夠避免同一線程多次獲取鎖時(shí)的死鎖現(xiàn)象呀袱。
/**
* 競(jìng)爭(zhēng)線程調(diào)用入口方法
*/
public synchronized void facadeMethod(){
// 處理業(yè)務(wù)
innerMethod();
}
public void innerMethod(){
// 處理業(yè)務(wù)
}
當(dāng)只在調(diào)用入口方法上添加 synchronized 鎖,內(nèi)部調(diào)用鏈所涉及的方法都不添加鎖郑叠,在線程競(jìng)爭(zhēng)條件下也是線程安全的夜赵。這種條件下即使 synchronized 不是可重入鎖,也不會(huì)發(fā)生死鎖乡革。原因如下:方法調(diào)用是以方法棧的形式調(diào)用的寇僧,在入口方法加鎖相當(dāng)于內(nèi)部調(diào)用鏈的方法都鎖的約束之下,因此是線程安全的沸版。
2嘁傀、非可重入鎖危害程度分析
假如 synchronized 不是可重入鎖,業(yè)務(wù)層有死鎖發(fā)生時(shí)视粮,應(yīng)用在測(cè)試環(huán)境壓測(cè)必然能夠發(fā)現(xiàn)细办,未進(jìn)入生產(chǎn)環(huán)境便可提前處理。因?yàn)檫@種死鎖是一種必然發(fā)生事件蕾殴,排查起來(lái)較為容易笑撞。
當(dāng)死鎖發(fā)生時(shí),第一步排查當(dāng)前鎖是否是可重入的钓觉,其次再考慮是否是業(yè)務(wù)層代碼邏輯本身存在缺陷茴肥。
/**
* 競(jìng)爭(zhēng)線程調(diào)用入口方法
*/
public synchronized void facadeMethod(){
// 處理業(yè)務(wù)
innerMethod();
}
public synchronized void innerMethod(){
// 處理業(yè)務(wù)
}
可重入鎖是對(duì)鎖的一次改良,提高了開(kāi)發(fā)效率是顯而易見(jiàn)的议谷,與此同時(shí)也給使用鎖的用戶造成不必要的困擾:在使用鎖的過(guò)程中炉爆,是否可重入并不是避免死鎖的充分條件。
(三)獨(dú)享鎖/共享鎖
獨(dú)享鎖是指該鎖一次只能被一個(gè)線程所持有卧晓;共享鎖是指該鎖可被多個(gè)線程所持有芬首。實(shí)現(xiàn)ReadWriteLock接口的鎖,其中讀鎖
是共享鎖逼裆、寫(xiě)鎖
是獨(dú)享鎖郁稍。
在內(nèi)置的鎖中,除了讀寫(xiě)鎖中的讀鎖是共享鎖胜宇,其余皆是獨(dú)享鎖耀怜。
1、降低鎖的顆粒度
競(jìng)爭(zhēng)線程在處理競(jìng)爭(zhēng)資源時(shí)有如下四種情形:讀讀桐愉、讀寫(xiě)财破、寫(xiě)讀、寫(xiě)寫(xiě)从诲,對(duì)于大部分應(yīng)用來(lái)說(shuō)左痢,讀操作的比寫(xiě)操作的頻度要高,更清楚的表述是在大部分時(shí)間里讀讀
是線程間處理競(jìng)爭(zhēng)資源形態(tài),因此降低鎖的顆粒度現(xiàn)實(shí)意義比較明顯俊性。
2略步、共享讀鎖與樂(lè)觀讀鎖
共享讀鎖是為了解決獨(dú)占鎖只能被一個(gè)線程占有的問(wèn)題,它支持多個(gè)線程同時(shí)持有鎖定页,本質(zhì)上屬于悲觀鎖的范疇趟薄。
樂(lè)觀讀鎖更為徹底,將加鎖的環(huán)節(jié)取消典徊,但通過(guò)特殊機(jī)制仍能夠保證線程安全杭煎。
加鎖和釋放鎖是一個(gè)重操作,因此樂(lè)觀讀鎖比共享讀鎖效率更高宫峦。
鎖的匯總
// 非公平可重入讀寫(xiě)鎖
ReentrantReadWriteLock unfairLock = new ReentrantReadWriteLock();
// 公平可重入讀寫(xiě)鎖
ReentrantReadWriteLock fairLock = new ReentrantReadWriteLock(true);
(四)樂(lè)觀鎖/悲觀鎖
樂(lè)觀鎖與悲觀鎖的內(nèi)涵是當(dāng)并發(fā)發(fā)生時(shí)處理并發(fā)同步的態(tài)度岔帽。悲觀鎖認(rèn)為當(dāng)并發(fā)發(fā)生時(shí),被鎖的對(duì)象一定會(huì)發(fā)生修改导绷,如果放任不管犀勒,并發(fā)操作一定會(huì)給業(yè)務(wù)帶來(lái)副作用。
悲觀鎖需要加鎖妥曲,樂(lè)觀鎖不加鎖但仍能通過(guò)一定機(jī)制保證線程安全贾费。
互斥鎖
、自旋鎖檐盟、讀寫(xiě)鎖
都屬于悲觀鎖褂萧。
1、典型樂(lè)觀鎖
嚴(yán)格意義來(lái)講葵萎,只有悲觀鎖才能稱之為鎖闻鉴,樂(lè)觀鎖本身不通過(guò)加鎖來(lái)解決并發(fā)問(wèn)題魁衙,因此稱之為樂(lè)觀“鎖”更合適喜庞。
樂(lè)觀“鎖”處理并發(fā)問(wèn)題有兩種常見(jiàn)方式:一是以AtomicInteger
為代表的原子操作類案狠,這種處理方式本身不加鎖,但仍能解決并發(fā)產(chǎn)生的問(wèn)題卷雕;二是樂(lè)觀鎖StampedLock類中的樂(lè)觀讀节猿。
(五)自旋鎖
自旋鎖是相對(duì)于互斥鎖而言的,本質(zhì)上屬于悲觀鎖的一種(仍然需要加鎖)漫雕。
1滨嘱、自旋鎖的原理
當(dāng)線程申請(qǐng)獲取鎖時(shí),發(fā)現(xiàn)已經(jīng)被其它線程占有浸间,此時(shí)不斷的循環(huán)嘗試獲取鎖太雨,直到獲取鎖成功。線程自旋獲取鎖需要消耗 CPU魁蒜,如果一直獲取不到鎖囊扳,線程會(huì)一直自旋煤墙,持續(xù)消耗 CPU。
自旋鎖是對(duì)線程申請(qǐng)獲取鎖時(shí)出現(xiàn)的阻塞與喚醒上下文切換的一種優(yōu)化宪拥,即用 CPU 資源換取線程狀態(tài)切換時(shí)間,當(dāng)線程通過(guò)自旋獲取鎖的時(shí)間超過(guò)普通的阻塞-喚醒調(diào)度時(shí)間铣减,那么就不適合選用自旋鎖她君。
2、自旋鎖使用場(chǎng)景及優(yōu)缺點(diǎn)
(1)使用場(chǎng)景
如果持有鎖的線程能在很短時(shí)間內(nèi)釋放鎖資源葫哗,選用自旋鎖非常合適缔刹。線程平均占有鎖的時(shí)間很短,其它線程稍微等待(自旋)便能立刻獲取鎖劣针,效率比阻塞-喚醒線程狀態(tài)切換高得多校镐。
一般而言,競(jìng)爭(zhēng)資源涉及內(nèi)存計(jì)算時(shí)捺典,占有鎖的時(shí)間平均都比較短鸟廓,適合自旋鎖;對(duì)于磁盤(pán)讀寫(xiě) IO 操作襟己、網(wǎng)絡(luò)操作等引谜,線程占有鎖的時(shí)間平均較長(zhǎng),不適合使用自旋鎖擎浴。
代碼塊或者輕量級(jí)方法员咽,線程競(jìng)爭(zhēng)不激烈的場(chǎng)景下,適合自旋鎖贮预。
(2)優(yōu)缺點(diǎn)
自旋鎖盡可能的減少線程的阻塞贝室,對(duì)于鎖的競(jìng)爭(zhēng)不激烈且占用鎖時(shí)間非常短的代碼塊來(lái)說(shuō)性能提升明顯。自旋的時(shí)間消耗會(huì)小于線程阻塞掛起再喚醒的操作的消耗仿吞,回避了線程兩次上下文切換滑频。
3、自旋鎖與樂(lè)觀鎖
自旋鎖與樂(lè)觀鎖的區(qū)別是很明顯的茫藏,很多地方常用 CAS 技術(shù)對(duì)兩者舉例误趴,以致于讓它們的邊界比較模糊。
鎖 | 樂(lè)觀(悲觀)鎖 | 獨(dú)占(共享)鎖 | 消耗 CPU 資源的目的 | 提升效率優(yōu)化核心點(diǎn) |
---|---|---|---|---|
自旋鎖 | 悲觀鎖 | 獨(dú)占鎖 | 申請(qǐng)獲取鎖 | 用 CPU 資源置換線程阻塞-喚醒調(diào)度時(shí)間 |
樂(lè)觀鎖 | 樂(lè)觀鎖 | 共享鎖 | 比較與交換 | 不加鎖务傲,如果需要處理線程問(wèn)題凉当,則采取相應(yīng)的措施 |
除了原子操作類中用樂(lè)觀鎖處理讀寫(xiě)外,StampedLock
類主要用到樂(lè)觀讀鎖售葡。
三看杭、關(guān)鍵字鎖
synchronized
關(guān)鍵字屬于內(nèi)置鎖,可作用于對(duì)象
和方法
挟伙。添加到方法上的鎖楼雹,鎖到在哪里?
對(duì)于實(shí)例方法,鎖添加到持有方法的實(shí)例上贮缅;對(duì)于類方法榨咐,鎖添加到類對(duì)象(Class 對(duì)象)上。
(一)感性認(rèn)識(shí)
關(guān)鍵字synchronized
創(chuàng)建的是一把可重入
的鎖谴供,不是簡(jiǎn)單的輕量級(jí)或者重量級(jí)的鎖块茁,也不是簡(jiǎn)單的公平與非公平鎖。
Java8 內(nèi)置的synchronized
是經(jīng)過(guò)優(yōu)化的鎖桂肌,有偏向鎖数焊、輕量級(jí)鎖、重量級(jí)鎖等狀態(tài)崎场。
重量級(jí)鎖影響性能的根本原因是伴隨著加鎖與釋放鎖佩耳,競(jìng)爭(zhēng)鎖的工作線程發(fā)生上下文切換。
1谭跨、公平性分析
鎖處于輕量級(jí)時(shí)干厚,因?yàn)椴淮嬖诰€程間獲取鎖的實(shí)質(zhì)性碰撞行為,理論情況下“競(jìng)爭(zhēng)”線程不存在饑餓狀態(tài)的發(fā)生饺蚊,因此屬于公平鎖萍诱。
鎖處于重量級(jí)時(shí),無(wú)法保證競(jìng)爭(zhēng)線程一定不存在饑餓狀態(tài)發(fā)生污呼,因此屬于非公平鎖裕坊。
2、非公平如何理解
使用 synchronized 加鎖的線程燕酷,沒(méi)有先進(jìn)先出的隊(duì)列機(jī)制保證有序獲取鎖籍凝,因此它是非公平鎖。
(1)競(jìng)爭(zhēng)線程隨機(jī)獲取鎖苗缩?
隨機(jī)必然伴隨著概率事件饵蒂,獲取鎖既有成功的概率也有失敗的概率,如果是嚴(yán)格隨機(jī)酱讶,理論情況下是不存在饑餓狀態(tài)發(fā)生的退盯,這種情況下也就不屬于非公平鎖之說(shuō)。
競(jìng)爭(zhēng)線程不是隨機(jī)獲取鎖泻肯,盡管從線程的角度看像是一種“隨機(jī)”行為渊迁,因此它是一把非公平鎖。
(2)競(jìng)爭(zhēng)線程可預(yù)測(cè)獲取鎖灶挟?
(重量級(jí)鎖)在競(jìng)爭(zhēng)鎖條件下必然存在操作系統(tǒng)級(jí)別的(線程阻塞與喚醒)系統(tǒng)調(diào)度行為琉朽。操作系統(tǒng)的調(diào)度是按照既定的規(guī)則進(jìn)行線程調(diào)度的,線程被操作系統(tǒng)喚醒稚铣,才有機(jī)會(huì)獲取鎖箱叁,因此可以粗略的理解獲取鎖的行為也是可以預(yù)測(cè)的墅垮。
(3)可預(yù)測(cè)獲取鎖是公平鎖?
假如操作系統(tǒng)是按照優(yōu)先級(jí)高低完成線程調(diào)度的耕漱,極端情況下算色,新申請(qǐng)獲取鎖的線程優(yōu)先級(jí)永遠(yuǎn)比等待隊(duì)列中線程優(yōu)先級(jí)要高,那么等待隊(duì)列必然會(huì)發(fā)生饑餓狀態(tài)螟够,因此盡管獲取鎖的行為是有規(guī)律的剃允、能夠預(yù)測(cè)的,它依然是非公平鎖齐鲤。
3、互斥鎖
互斥鎖即重量級(jí)鎖椒楣,互斥依靠通過(guò)操作系統(tǒng)來(lái)實(shí)現(xiàn)给郊。
互斥的表現(xiàn)形式如下:當(dāng)多線程競(jìng)爭(zhēng)資源條件下,未獲得鎖的其它線程均處于阻塞狀態(tài)捧灰,當(dāng)持有鎖的線程釋放鎖后淆九,阻塞狀態(tài)的線程被喚醒競(jìng)爭(zhēng)獲取鎖,未獲取成功的鎖繼續(xù)阻塞毛俏,如此循環(huán)炭庙。線程調(diào)度需要操作系統(tǒng)切換上下文,占用 CPU 時(shí)間煌寇,影響性能焕蹄。
操作系統(tǒng) CPU 時(shí)間片大致可分為兩類,一是工作時(shí)間阀溶;二是調(diào)度時(shí)間腻脏,調(diào)度時(shí)間越長(zhǎng)相應(yīng)的便會(huì)縮短工作時(shí)長(zhǎng)。
(二)鎖的膨脹
這里不講鎖的膨脹過(guò)程银锻,只講鎖在膨脹過(guò)程中涉及的中間狀態(tài)永品,以及如何理解。鎖的膨脹是單向的击纬,只能升級(jí)不能降級(jí)鼎姐。
1、偏向鎖
線程間不存在鎖的競(jìng)爭(zhēng)行為更振,至多只有一個(gè)線程有獲取鎖的需求炕桨,常見(jiàn)場(chǎng)景為單線程程序
。
2殃饿、輕量級(jí)鎖
線程間存在鎖的偽競(jìng)爭(zhēng)
行為谋作,即同一時(shí)刻絕對(duì)不會(huì)存在兩個(gè)線程申請(qǐng)獲取鎖,各線程盡管都有使用鎖的需求乎芳,但是是交替使用鎖遵蚜。
3帖池、重量級(jí)鎖
線程間存在鎖的實(shí)質(zhì)性競(jìng)爭(zhēng)行為,線程間都有獲取鎖的需求吭净,但是時(shí)間不可交錯(cuò)睡汹,互斥鎖的阻塞等待。
四寂殉、接口鎖
(一)Lock
Lock是所有接口實(shí)現(xiàn)類的父類接口囚巴,定義了鎖操作的基本規(guī)范。
public interface Lock {
// 阻塞等待獲取鎖
void lock();
// 阻塞等待獲取鎖(可相應(yīng)中斷)
void lockInterruptibly() throws InterruptedException;
// 非阻塞獲取鎖
boolean tryLock();
// 等待指定時(shí)間非阻塞獲取鎖
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 釋放鎖
void unlock();
}
(二)StampedLock
1友扰、StampedLock優(yōu)勢(shì)
高性能
StampedLock在讀線程非常多而寫(xiě)線程較少的場(chǎng)景下性能非常高彤叉,樂(lè)觀讀鎖屬于無(wú)鎖編程,可以簡(jiǎn)單理解為沒(méi)有加鎖村怪。
回避寫(xiě)鎖饑餓
非公平讀寫(xiě)鎖在讀多寫(xiě)少的場(chǎng)景下可能發(fā)生寫(xiě)鎖饑餓秽浇,而在高并發(fā)的場(chǎng)景下,都會(huì)優(yōu)先使用非公平鎖甚负。StampedLock能解決這個(gè)矛盾問(wèn)題:既能使用非公平讀寫(xiě)鎖柬焕,又能回避寫(xiě)鎖饑餓。
回避寫(xiě)鎖饑餓的機(jī)制是能將任一讀鎖轉(zhuǎn)化為寫(xiě)鎖梭域。
2斑举、典型應(yīng)用
排它寫(xiě)鎖
/**
* 排它寫(xiě)鎖(an exclusively locked method)
*/
void move(double deltaX, double deltaY) {
long stamp = stampedLock.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
stampedLock.unlockWrite(stamp);
}
}
排它寫(xiě)鎖能安全的修改數(shù)據(jù),在為釋放鎖之前病涨,其它線程無(wú)法獲取鎖富玷。
此種方式可能會(huì)發(fā)生寫(xiě)鎖饑餓的情況。
排它寫(xiě)鎖(改進(jìn))
普通寫(xiě)鎖可能會(huì)發(fā)生寫(xiě)鎖饑餓既穆,下面方式能夠避免寫(xiě)鎖饑餓——讀鎖轉(zhuǎn)寫(xiě)鎖凌彬。
/**
* 無(wú)饑餓寫(xiě)鎖
*/
void moveNoHunger(double deltaX, double deltaY) {
long stamp = stampedLock.readLock();
try {
while (x == 0.0 && y == 0.0) {
long ws = stampedLock.tryConvertToWriteLock(stamp);
if (ws != 0L) {
stamp = ws;
x += deltaX;
y += deltaY;
break;
} else {
stampedLock.unlockRead(stamp);
stamp = stampedLock.writeLock();
}
}
} finally {
stampedLock.unlock(stamp);
}
}
樂(lè)觀鎖
/**
* 樂(lè)觀讀鎖
*/
double distanceFromOrigin() { // A read-only method
long stamp = stampedLock.tryOptimisticRead();
double currentX = x, currentY = y;
if (!stampedLock.validate(stamp)) {
stamp = stampedLock.readLock();
try {
currentX = x;
currentY = y;
} finally {
stampedLock.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
喜歡本文點(diǎn)個(gè)??贊??支持一下,如有需要循衰,可通過(guò)微信
dream4s
與我聯(lián)系铲敛。相關(guān)源碼在GitHub,視頻講解在B站会钝,本文收藏在博客天地伐蒋。