java锁(公平锁和非公平锁、可重入锁(又名递归锁)、自旋锁、独占锁(写)/共享锁(读)/互斥锁、读写锁)
前言
本文對Java的一些鎖的概念和實現(xiàn)做個整理,涉及:公平鎖和非公平鎖、可重入鎖(又名遞歸鎖)、自旋鎖、獨占鎖(寫)/共享鎖(讀)/互斥鎖、讀寫鎖
公平鎖和非公平鎖
概念
- 公平鎖是指多個線程按照申請鎖的順序來獲取鎖。類似于進(jìn)程的FCFS(先來先服務(wù)),隊列的FIFO(先來先輸出)
- 非公平鎖是指在多線程獲取鎖的順序并不是按照申請鎖的順序,有可能后申請的線程比先申請的線程優(yōu)先獲取到鎖,在高并發(fā)的情況下,有可能造成優(yōu)先級反轉(zhuǎn)或者饑餓現(xiàn)象(長時間無法獲得鎖)
區(qū)別
公平鎖:公平鎖就很公平。在并發(fā)環(huán)境中,每個線程在獲取鎖時會先查看這個鎖維護(hù)的等待隊列,如果為空,即當(dāng)前線程是等待隊列中的第一個就占有鎖,否則就加入等待隊列中,根據(jù)FIFO的規(guī)則等待占有鎖。
非公平鎖:這個就很不公平了。在并發(fā)環(huán)境中,每個線程一上來不管三七二十一就嘗試搶占鎖,沒有搶到鎖再采用類似公平鎖的那種機(jī)制。
實現(xiàn)
- sychronized就是一種非公平鎖。
- ReentrantLock通過構(gòu)造函數(shù)指定該鎖是否是公平鎖(true為公平鎖,false為非公平鎖),默認(rèn)是非公平鎖(false),非公平鎖的優(yōu)點在于吞吐量比公平鎖大。
可重入鎖
概念
可重入鎖也叫遞歸鎖,指同一個線程在獲得了外層函數(shù)的鎖后,內(nèi)層遞歸函數(shù)仍然能獲取該鎖的代碼。也就是說,同一線程在外層方法獲得鎖后,在進(jìn)入內(nèi)層方法會自動獲取鎖。
作用
可重入鎖最大的作用就是避免死鎖
實現(xiàn)
ReentrantLock和synchronized都是典型的可重入鎖,給出ReentrantLock和synchronized同步鎖的代碼實現(xiàn):
1.使用sychronized關(guān)鍵字演示可重入鎖:
public class SychronizedDemo {public static void main(String[] args) {Phone phone = new Phone();new Thread(()->{phone.call();},"thread1").start();new Thread(()->{try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}phone.sendSMS();},"thread2").start();}
}class Phone{public synchronized void call(){System.out.println(Thread.currentThread().getName() + "\t 撥打電話");try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}sendSMS();}public synchronized void sendSMS(){System.out.println(Thread.currentThread().getName() + "\t 發(fā)短信");}
}
代碼解讀:
這段代碼的輸出結(jié)果是多少呢?有人可能會認(rèn)為是:
thread1 撥打電話
thread2 發(fā)短信
thread1 發(fā)短信
因為在線程1獲得了call方法的鎖輸出了“thread1撥打電話”之后睡眠了2秒,然后這時線程2肯定能夠槍到sendSMS方法的鎖輸出“thread2發(fā)短信”最后才是線程1醒了獲取到sendSMS方法的鎖輸出“thread1發(fā)短信”。
這種想法是沒錯,但不要忘了sychronized是可重入鎖,在線程1得到call方法的鎖后就已經(jīng)得到了其方法內(nèi)部的sendSMS方法的鎖,這時線程2去執(zhí)行sendSMS方法的時候會發(fā)現(xiàn)該方法是出于鎖住的狀態(tài)然后線程2阻塞,等到線程1執(zhí)行完call方法釋放sendSMS方法的鎖之后線程2才能繼續(xù)執(zhí)行。所以正確的輸出結(jié)果是:
thread1 撥打電話
thread1 發(fā)短信
thread2 發(fā)短信
2.使用ReentrantLock演示可重入鎖:
public class ReentrantLockDemo {public static void main(String[] args) {Phone2 phone = new Phone2();new Thread(()->{phone.call();},"thread1").start();new Thread(()->{try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}phone.sendSMS();},"thread2").start();}
}class Phone2{Lock lock = new ReentrantLock();public void call(){try{lock.lock();System.out.println(Thread.currentThread().getName() + "\t 撥打電話");try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}sendSMS();}finally {lock.unlock();}}public void sendSMS(){try {lock.lock();System.out.println(Thread.currentThread().getName() + "\t 發(fā)短信");}finally {lock.unlock();}}
}
輸出結(jié)果和使用sychronized一樣。
自旋鎖
概念
自旋鎖是指嘗試獲取鎖的線程不會立即阻塞,而是采用循環(huán)的方式去獲取鎖,這樣的好處是減少線程上下文切換帶來的消耗,但是循環(huán)會帶來CPU的消耗。
實現(xiàn)
其實CAS的核心實現(xiàn)類UnSafe采用的就是自旋鎖:
public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {var5 = this.getIntVolatile(var1, var2);} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));return var5;}
自寫自旋鎖實現(xiàn):
public class SpinLockDemo {private AtomicReference<Thread> atomicReference = new AtomicReference<>();public void myLock(){Thread thread = Thread.currentThread();System.out.println(thread.getName() + " come in");while (!atomicReference.compareAndSet(null,thread)){}System.out.println(thread.getName() + " 獲得鎖");}public void myUnLock(){Thread thread = Thread.currentThread();atomicReference.compareAndSet(thread,null);System.out.println(thread.getName() + " myUnLock");}public static void main(String[] args) {SpinLockDemo spinLockDemo = new SpinLockDemo();new Thread(()->{spinLockDemo.myLock();try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}spinLockDemo.myUnLock();},"thread1").start();new Thread(()->{try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}spinLockDemo.myLock();spinLockDemo.myUnLock();},"thread2").start();}
}
輸出結(jié)果:
thread1 come in
thread1 獲得鎖
thread2 come in
thread1 myUnLock
thread2 獲得鎖
thread2 myUnLock
獨占鎖(寫)/共享鎖(讀)/互斥鎖
概念
獨占鎖:指該鎖只能被一個線程所持有,如ReentrantLock和sychronized都是獨占鎖
共享鎖:指該鎖可以被多個線程所持有,如ReentrantReadWriteLock其讀鎖是共享鎖,寫是獨占鎖。
讀的共享鎖可以保證并發(fā)讀是非常高效的。
互斥鎖:互斥鎖是一種簡單的加鎖的方法來控制對共享資源的訪問,互斥鎖只有兩種狀態(tài),即上鎖(lock)和解鎖(unlock)。如ReentrantLock和sychronized都是互斥鎖
實現(xiàn)
下面是使用ReentrantReadWriteLock實現(xiàn)讀時共享寫時獨占
public class ReadWriteLockDemo {public static void main(String[] args) {MyCaChe myCaChe = new MyCaChe();for (int i = 1; i <= 5; i++) {final int temp = i;new Thread(() -> {myCaChe.put(temp + "", temp);}, String.valueOf(i)).start();}for (int i = 1; i <= 5; i++) {int finalI = i;new Thread(() -> {myCaChe.get(finalI + "");}, String.valueOf(i)).start();}}
}class MyCaChe {private volatile Map<String, Object> map = new HashMap<>();private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();public void put(String key, Object value) {reentrantReadWriteLock.writeLock().lock();try {System.out.println(Thread.currentThread().getName() + "\t正在寫入" + key);//模擬網(wǎng)絡(luò)延時try {TimeUnit.MICROSECONDS.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}map.put(key, value);System.out.println(Thread.currentThread().getName() + "\t完成寫入" + key);} finally {reentrantReadWriteLock.writeLock().unlock();}}public void get(String key) {reentrantReadWriteLock.readLock().lock();try {System.out.println(Thread.currentThread().getName() + "\t正在讀取" + key);//模擬網(wǎng)絡(luò)延時try {TimeUnit.MICROSECONDS.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}Object result = map.get(key);System.out.println(Thread.currentThread().getName() + "\t完成讀取" + key);} finally {reentrantReadWriteLock.readLock().unlock();}}public void clearCaChe() {map.clear();}
}
代碼
本文所涉及的所有代碼都在我的GitHub上:代碼
總結(jié)
以上是生活随笔為你收集整理的java锁(公平锁和非公平锁、可重入锁(又名递归锁)、自旋锁、独占锁(写)/共享锁(读)/互斥锁、读写锁)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Map再整理,从底层源码探究HashMa
- 下一篇: Http请求之优雅的RestTempla