本篇文章主要是記錄自己的學習筆記,主要內容是:公平鎖、非公平鎖、可重入鎖、遞歸鎖、自旋鎖的理解,並實現一個自旋鎖。
公平和非公平鎖
(1)公平鎖和非公平鎖是什么?
公平鎖:是指多個線程按照申請鎖的順序來獲取鎖,類似排隊打飯,先來后到。
非公平鎖:是指多個線程獲取鎖的順序並不是按照申請鎖的循序,有可能后申請的線程比先申請的線程優先獲取鎖。但是,在高並發的情況下,有可能會造成優先級反轉或者飢餓現象。
(2)公平鎖和非公平鎖的區別是什么?
並發包中ReentrantLock的創建可以指定構造函數的boolean類型來得到公平鎖。
公平鎖和非公平鎖兩者的區別:
公平鎖:線程按照他們申請鎖的順序獲取鎖,公平鎖就是很公平,在並發環境下,每個線程在獲取鎖時會先查看此鎖維護的等待隊列,如果隊列為空,獲取當前線程時等待隊列的第一個,就占有鎖。否則,就會加入到等待隊列中,以后會按照FIFO的規則從隊列中獲取鎖。
非公平鎖:非公平所比較粗魯,上來就嘗試占有鎖,如果嘗試失敗,就采用類似公平鎖的方式獲取鎖。
補充:ReentrantLock即使通過構造函數指定該鎖是否是公平鎖,默認是非公平鎖。非公平鎖的優點就是吞吐量比公平鎖大。同樣的synchronized也是一種非公平鎖。
可重入鎖(又名遞歸鎖)
可重入鎖定義:指的是同一線程外層函數獲得鎖忠厚,內層遞歸函數仍能獲取該鎖。在同一個線程在外層黨法獲取鎖的時候,在進入內層方法會自動獲取鎖。也就是說。線程可以進入任何一個它已經擁有的鎖所同步着的代碼塊。
ReetrantLock和synchronized就是一個典型的可重入鎖,其最大的作用就是避免死鎖。
下面我們用代碼來進行驗證:
class Phone{ public synchronized void sendMessage(){ System.out.println(Thread.currentThread().getId() + " \t " + " invoked sendMessage"); sendEmail(); } public synchronized void sendEmail(){ System.out.println(Thread.currentThread().getId() + " \t " + " ###### invoked sendEmail"); System.out.println("----------------------------------- 分割線 -----------------------------------"); System.out.println(); } } public class ReetrantLockDemo { public static void main(String[] args) { Phone phone = new Phone(); new Thread(()->{ phone.sendMessage(); }).start(); new Thread(()->{ phone.sendMessage(); }).start(); } }
輸出結果:
對於RetrantLock同樣可得上述結果:
class Phone{ Lock lock = new ReentrantLock(); public void sendMessage(){ try { lock.lock(); System.out.println(Thread.currentThread().getId() + " \t " + " invoked sendMessage"); sendEmail(); }finally { lock.unlock(); } } public synchronized void sendEmail(){ try { lock.lock(); System.out.println(Thread.currentThread().getId() + " \t " + " ###### invoked sendEmail"); System.out.println("----------------------------------- 分割線 -----------------------------------"); System.out.println(); }finally { lock.unlock(); } } } public class ReetrantLockDemo { public static void main(String[] args) throws InterruptedException { Phone phone = new Phone(); new Thread(()->{ phone.sendMessage(); }).start(); new Thread(()->{ phone.sendMessage(); }).start(); } }
輸出結果同上。
自旋鎖(spinlock)
自旋鎖:是指嘗試獲取鎖的線程不會立即阻塞,而是采用循環的方式去嘗試獲取鎖,這樣的好處是減少線程上下文切換的消耗,缺點是:循環會消耗CPU資源。
我們學習過的CAS原理,就是采用了自旋鎖的思想:
了解了自旋鎖的原理之后,我們自己實現一個自旋鎖。
public class SpinLockDemo { AtomicReference<Thread> atomicReference = new AtomicReference<>(); public void myLock(){ System.out.println(Thread.currentThread().getName() + "\t come in"); Thread thread = Thread.currentThread(); while (!atomicReference.compareAndSet(null, thread)){ } } public void myUnLock(){ Thread thread = Thread.currentThread(); atomicReference.compareAndSet(thread, null); System.out.println(Thread.currentThread().getName() + "\t leave out"); } public static void main(String[] args) { SpinLockDemo demo = new SpinLockDemo(); new Thread(()->{ demo.myLock(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } demo.myUnLock(); }, "AAA").start(); new Thread(()->{ demo.myLock(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } demo.myUnLock(); }, "BBB").start(); } }
輸出結果:
獨占鎖(寫鎖)/共享鎖(讀鎖)/互斥鎖
獨占鎖:指該鎖一次只能被一個線程所持有。對ReentrantLock和synchronized來說,它們都是獨占鎖。
共享鎖:指該鎖可以被多個線程所持有。
對ReentrantReadWriteLock來說,讀鎖是共享鎖,寫鎖是獨占鎖。
讀鎖的共享鎖可以保證並發讀是非常高效的,讀寫,寫讀,寫寫的過程是互斥的。
也就是說,多個線程同時讀取一個資源類沒有任何問題,所以為了滿足並發量,讀取共享資源應該可以同時進行。但是,如果有一個線程想去寫共享資源,就不應該又其他的線程對資源進行讀或者寫。
總結:讀-讀可共存
讀-寫不可以共存
寫-寫不可以共存
代碼實現小例子:
class CacheResource{ private volatile Map<String, Object> map = new HashMap<>(); private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); public void put(String key, Object value){ try { reentrantReadWriteLock.writeLock().lock(); System.out.println("--------------------------------------------------------------"); System.out.println(Thread.currentThread().getId() + " \t " + "正在寫入" + key); }finally { System.out.println(Thread.currentThread().getId() + " \t " + "寫入成功" + key); System.out.println("--------------------------------------------------------------"); reentrantReadWriteLock.writeLock().unlock(); } } public void get(String key){ try { reentrantReadWriteLock.readLock().lock(); System.out.println(Thread.currentThread().getId() + " \t " + "正在讀取 \t" + key); }finally { System.out.println(Thread.currentThread().getId() + " \t " + "讀取成功 \t" + key); reentrantReadWriteLock.readLock().unlock(); } } } public class ReentrantReadWriteLockDemo { public static void main(String[] args) { CacheResource resource = new CacheResource(); for (int i = 0; i < 5; i++){ final int tmp = i; new Thread(()->{ resource.put(tmp + "", ""); }).start(); } for (int i = 0; i < 5; i++){ final int tmp = i; new Thread(()->{ resource.get(tmp + ""); }).start(); } } }
輸出結果:
-------------------------------------------------------------- 12 正在寫入1 12 寫入成功1 -------------------------------------------------------------- -------------------------------------------------------------- 11 正在寫入0 11 寫入成功0 -------------------------------------------------------------- -------------------------------------------------------------- 13 正在寫入2 13 寫入成功2 -------------------------------------------------------------- -------------------------------------------------------------- 14 正在寫入3 14 寫入成功3 -------------------------------------------------------------- -------------------------------------------------------------- 15 正在寫入4 15 寫入成功4 -------------------------------------------------------------- 16 正在讀取 0 16 讀取成功 0 18 正在讀取 2 18 讀取成功 2 19 正在讀取 3 19 讀取成功 3 20 正在讀取 4 20 讀取成功 4 17 正在讀取 1 17 讀取成功 1 Process finished with exit code 0
到這里,本篇文章就結束了,雖然寫博客會花費一定的時間,但是可以加深自己對知識點的理解,便於日后復習,要堅持。