前言
在之前的文章《一文徹底搞懂面試中常問(wèn)的各種“鎖”》中介紹了Java中的各種“鎖”,可能對(duì)于不是很了解這些概念的同學(xué)來(lái)說(shuō)會(huì)覺(jué)得有點(diǎn)繞涣楷,所以我決定拆分出來(lái)分唾,逐步詳細(xì)的介紹一下這些鎖的來(lái)龍去脈,這篇文章就先來(lái)會(huì)一會(huì)“自旋鎖”狮斗。
正文
出現(xiàn)原因
在我們的程序中绽乔,如果存在著大量的互斥同步代碼,當(dāng)出現(xiàn)高并發(fā)的時(shí)候碳褒,系統(tǒng)內(nèi)核態(tài)就需要不斷的去掛起線程和恢復(fù)線程,頻繁的此類(lèi)操作會(huì)對(duì)我們系統(tǒng)的并發(fā)性能有一定影響沙峻。同時(shí)聰明的JVM開(kāi)發(fā)團(tuán)隊(duì)也發(fā)現(xiàn)睦授,在程序的執(zhí)行過(guò)程中鎖定“共享資源“的時(shí)間片是極短的,如果僅僅是為了這點(diǎn)時(shí)間而去不斷掛起摔寨、恢復(fù)線程的話去枷,消耗的時(shí)間可能會(huì)更長(zhǎng),那就“撿了芝麻丟了西瓜”了是复。
而在一個(gè)多核的機(jī)器中删顶,多個(gè)線程是可以并行執(zhí)行的。那么當(dāng)后面請(qǐng)求鎖的線程沒(méi)拿到鎖的時(shí)候淑廊,不掛起線程逗余,而是繼續(xù)占用處理器的執(zhí)行時(shí)間,讓當(dāng)前線程執(zhí)行一個(gè)忙循環(huán)(自旋操作)季惩,也就是不斷在盯著持有鎖的線程是否已經(jīng)釋放鎖猎荠,那么這就是傳說(shuō)中的自旋鎖了坚弱。
自旋鎖開(kāi)啟
雖然在JDK1.4.2的時(shí)候就引入了自旋鎖,但是需要使用“-XX:+UseSpinning”參數(shù)來(lái)開(kāi)啟关摇。在到了JDK1.6以后荒叶,就已經(jīng)是默認(rèn)開(kāi)啟了。下面我們自己來(lái)實(shí)現(xiàn)一個(gè)基于CAS的簡(jiǎn)易版的自旋鎖输虱。
public class SimpleSpinningLock {
/**
* 持有鎖的線程些楣,null表示鎖未被線程持有
*/
private AtomicReference<Thread> ref = new AtomicReference<>();
public void lock(){
Thread currentThread = Thread.currentThread();
while(!ref.compareAndSet(null, currentThread)){
//當(dāng)ref為null的時(shí)候compareAndSet返回true,反之為false
//通過(guò)循環(huán)不斷的自旋判斷鎖是否被其他線程持有
}
}
public void unLock() {
Thread cur = Thread.currentThread();
if(ref.get() != cur){
//exception ...
}
ref.set(null);
}
}
簡(jiǎn)簡(jiǎn)單單幾行代碼就實(shí)現(xiàn)了一個(gè)簡(jiǎn)陋的自旋鎖宪睹,下面我們來(lái)測(cè)試一下
public class TestLock {
static int count = 0;
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(100);
CountDownLatch countDownLatch = new CountDownLatch(100);
SimpleSpinningLock simpleSpinningLock = new SimpleSpinningLock();
for (int i = 0 ; i < 100 ; i++){
executorService.execute(new Runnable() {
@Override
public void run() {
simpleSpinningLock.lock();
++count;
simpleSpinningLock.unLock();
countDownLatch.countDown();
}
}
);
}
countDownLatch.await();
System.out.println(count);
}
}
// 多次執(zhí)行輸出均為:100 愁茁,實(shí)現(xiàn)了鎖的基本功能
通過(guò)上面的代碼可以看出,自旋就是在循環(huán)判斷條件是否滿足亭病,那么會(huì)有什么問(wèn)題嗎鹅很?如果鎖被占用很長(zhǎng)時(shí)間的話,自旋的線程等待的時(shí)間也會(huì)變長(zhǎng)罪帖,白白浪費(fèi)掉處理器資源促煮。因此在JDK中,自旋操作默認(rèn)10次整袁,我們可以通過(guò)參數(shù)“-XX:PreBlockSpin”來(lái)設(shè)置菠齿,當(dāng)超過(guò)來(lái)此參數(shù)的值,則會(huì)使用傳統(tǒng)的線程掛起方式來(lái)等待鎖釋放坐昙。
自適應(yīng)自旋鎖
隨著JDK的更新绳匀,在1.6的時(shí)候,又出現(xiàn)了一個(gè)叫做“自適應(yīng)自旋鎖”的玩意炸客。它的出現(xiàn)使得自旋操作變得聰明起來(lái)疾棵,不再跟之前一樣死板。所謂的“自適應(yīng)”意味著對(duì)于同一個(gè)鎖對(duì)象痹仙,線程的自旋時(shí)間是根據(jù)上一個(gè)持有該鎖的線程的自旋時(shí)間以及狀態(tài)來(lái)確定的陋桂。例如對(duì)于A鎖對(duì)象來(lái)說(shuō),如果一個(gè)線程剛剛通過(guò)自旋獲得到了鎖蝶溶,并且該線程也在運(yùn)行中嗜历,那么JVM會(huì)認(rèn)為此次自旋操作也是有很大的機(jī)會(huì)可以拿到鎖,因此它會(huì)讓自旋的時(shí)間相對(duì)延長(zhǎng)抖所。但是如果對(duì)于B鎖對(duì)象自旋操作很少成功的話梨州,JVM甚至可能直接忽略自旋操作。因此田轧,自適應(yīng)自旋鎖是一個(gè)更加智能暴匠,對(duì)我們的業(yè)務(wù)性能更加友好的一個(gè)鎖。
結(jié)語(yǔ)
本來(lái)想著在一篇文章里面把“自旋鎖”傻粘,“鎖消除”每窖,“鎖粗化”等一些鎖優(yōu)化的概念都介紹完成的帮掉,但是發(fā)現(xiàn)可能篇幅會(huì)比較大,對(duì)于沒(méi)怎么接觸過(guò)這一塊的同學(xué)來(lái)說(shuō)理解起來(lái)會(huì)比較吃力窒典,所以決定分開(kāi)多個(gè)章節(jié)介紹蟆炊,希望大家都不懂的地方可以多看幾遍,慢慢體會(huì)瀑志,相信你會(huì)有所收獲的涩搓。
讀者福利
分享免費(fèi)學(xué)習(xí)資料
針對(duì)于Java程序員,我這邊準(zhǔn)備免費(fèi)的Java架構(gòu)學(xué)習(xí)資料(里面有高可用劈猪、高并發(fā)昧甘、高性能及分布式、Jvm性能調(diào)優(yōu)战得、MyBatis充边,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個(gè)知識(shí)點(diǎn)的架構(gòu)資料)
為什么某些人會(huì)一直比你優(yōu)秀,是因?yàn)樗旧砭秃軆?yōu)秀還一直在持續(xù)努力變得更優(yōu)秀常侦,而你是不是還在滿足于現(xiàn)狀內(nèi)心在竊喜浇冰!希望讀到這的您能點(diǎn)個(gè)小贊和關(guān)注下我,以后還會(huì)更新技術(shù)干貨刮吧,謝謝您的支持!
資料領(lǐng)取方式:加入Java技術(shù)交流群963944895
掖蛤,點(diǎn)擊加入群聊杀捻,私信管理員即可免費(fèi)領(lǐng)取
怎么提高代碼質(zhì)量?——來(lái)自阿里P8架構(gòu)師的研發(fā)經(jīng)驗(yàn)總結(jié)
阿里P8分享Java架構(gòu)師的學(xué)習(xí)路線蚓庭,第六點(diǎn)尤為重要
每個(gè)Java開(kāi)發(fā)者應(yīng)該知道的八個(gè)工具
想面試Java架構(gòu)師致讥?這些最基本的東西你都會(huì)了嗎?
畫(huà)個(gè)圖來(lái)找你的核心競(jìng)爭(zhēng)力器赞,變中年危機(jī)為加油站