java锁(公平锁和非公平锁、可重入锁(又名递归锁)、自旋锁、独占锁(写)/共享锁(读)/互斥锁、读写锁)
前言
本文對Java的一些鎖的概念和實現做個整理,涉及:公平鎖和非公平鎖、可重入鎖(又名遞歸鎖)、自旋鎖、獨占鎖(寫)/共享鎖(讀)/互斥鎖、讀寫鎖
公平鎖和非公平鎖
概念
- 公平鎖是指多個線程按照申請鎖的順序來獲取鎖。類似于進程的FCFS(先來先服務),隊列的FIFO(先來先輸出)
- 非公平鎖是指在多線程獲取鎖的順序并不是按照申請鎖的順序,有可能后申請的線程比先申請的線程優先獲取到鎖,在高并發的情況下,有可能造成優先級反轉或者饑餓現象(長時間無法獲得鎖)
區別
公平鎖:公平鎖就很公平。在并發環境中,每個線程在獲取鎖時會先查看這個鎖維護的等待隊列,如果為空,即當前線程是等待隊列中的第一個就占有鎖,否則就加入等待隊列中,根據FIFO的規則等待占有鎖。
非公平鎖:這個就很不公平了。在并發環境中,每個線程一上來不管三七二十一就嘗試搶占鎖,沒有搶到鎖再采用類似公平鎖的那種機制。
實現
- sychronized就是一種非公平鎖。
- ReentrantLock通過構造函數指定該鎖是否是公平鎖(true為公平鎖,false為非公平鎖),默認是非公平鎖(false),非公平鎖的優點在于吞吐量比公平鎖大。
可重入鎖
概念
可重入鎖也叫遞歸鎖,指同一個線程在獲得了外層函數的鎖后,內層遞歸函數仍然能獲取該鎖的代碼。也就是說,同一線程在外層方法獲得鎖后,在進入內層方法會自動獲取鎖。
作用
可重入鎖最大的作用就是避免死鎖
實現
ReentrantLock和synchronized都是典型的可重入鎖,給出ReentrantLock和synchronized同步鎖的代碼實現:
1.使用sychronized關鍵字演示可重入鎖:
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 發短信");}
}
代碼解讀:
這段代碼的輸出結果是多少呢?有人可能會認為是:
thread1 撥打電話
thread2 發短信
thread1 發短信
因為在線程1獲得了call方法的鎖輸出了“thread1撥打電話”之后睡眠了2秒,然后這時線程2肯定能夠槍到sendSMS方法的鎖輸出“thread2發短信”最后才是線程1醒了獲取到sendSMS方法的鎖輸出“thread1發短信”。
這種想法是沒錯,但不要忘了sychronized是可重入鎖,在線程1得到call方法的鎖后就已經得到了其方法內部的sendSMS方法的鎖,這時線程2去執行sendSMS方法的時候會發現該方法是出于鎖住的狀態然后線程2阻塞,等到線程1執行完call方法釋放sendSMS方法的鎖之后線程2才能繼續執行。所以正確的輸出結果是:
thread1 撥打電話
thread1 發短信
thread2 發短信
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 發短信");}finally {lock.unlock();}}
}
輸出結果和使用sychronized一樣。
自旋鎖
概念
自旋鎖是指嘗試獲取鎖的線程不會立即阻塞,而是采用循環的方式去獲取鎖,這樣的好處是減少線程上下文切換帶來的消耗,但是循環會帶來CPU的消耗。
實現
其實CAS的核心實現類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;}
自寫自旋鎖實現:
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();}
}
輸出結果:
thread1 come in
thread1 獲得鎖
thread2 come in
thread1 myUnLock
thread2 獲得鎖
thread2 myUnLock
獨占鎖(寫)/共享鎖(讀)/互斥鎖
概念
獨占鎖:指該鎖只能被一個線程所持有,如ReentrantLock和sychronized都是獨占鎖
共享鎖:指該鎖可以被多個線程所持有,如ReentrantReadWriteLock其讀鎖是共享鎖,寫是獨占鎖。
讀的共享鎖可以保證并發讀是非常高效的。
互斥鎖:互斥鎖是一種簡單的加鎖的方法來控制對共享資源的訪問,互斥鎖只有兩種狀態,即上鎖(lock)和解鎖(unlock)。如ReentrantLock和sychronized都是互斥鎖
實現
下面是使用ReentrantReadWriteLock實現讀時共享寫時獨占
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);//模擬網絡延時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);//模擬網絡延時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上:代碼
總結
以上是生活随笔為你收集整理的java锁(公平锁和非公平锁、可重入锁(又名递归锁)、自旋锁、独占锁(写)/共享锁(读)/互斥锁、读写锁)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Map再整理,从底层源码探究HashMa
- 下一篇: Http请求之优雅的RestTempla