公平鎖、非公平鎖、可重入鎖、遞迴鎖、自旋鎖?手寫自旋鎖
1、公平鎖、非公平鎖
-
是什麼
公平鎖就是先來後到、非公平鎖就是允許加塞,
Lock lock = new ReentrantLock(Boolean fair);
預設非公平。-
**==公平鎖==**是指多個執行緒按照申請鎖的順序來獲取鎖,類似排隊打飯。
-
**==非公平鎖==**是指多個執行緒獲取鎖的順序並不是按照申請鎖的順序,有可能後申請的執行緒優先獲取鎖,在高併發的情況下,有可能會造成優先順序反轉或者節現象。
-
-
兩者區別
-
公平鎖:Threads acquire a fair lock in the order in which they requested it
公平鎖,就是很公平,在併發環境中,每個執行緒在獲取鎖時,會先檢視此鎖維護的等待佇列,如果為空,或者當前執行緒就是等待佇列的第一個,就佔有鎖,否則就會加入到等待佇列中,以後會按照FIFO的規則從佇列中取到自己。
-
非公平鎖:a nonfair lock permits barging: threads requesting a lock can jump ahead of the queue of waiting threads if the lock happens to be available when it is requested.
非公平鎖比較粗魯,上來就直接嘗試佔有額,如果嘗試失敗,就再採用類似公平鎖那種方式。
-
-
other
對Java ReentrantLock而言,通過建構函式指定該鎖是否公平,磨粉是非公平鎖,非公平鎖的優點在於吞吐量比公平鎖大
對Synchronized而言,是一種非公平鎖
2、可重入所(遞迴鎖)
-
遞迴鎖是什麼
指的時同一執行緒外層函式獲得鎖之後,內層遞迴函式仍然能獲取該鎖的程式碼,在同一個執行緒在外層方法獲取鎖的時候,在進入內層方法會自動獲取鎖,也就是說,==執行緒可以進入任何一個它已經擁有的鎖所同步著的程式碼塊==
-
ReentrantLock/Synchronized 就是一個典型的可重入鎖
-
可重入鎖最大的作用是避免死鎖
-
程式碼示例
package com.jian8.juc.lock; #### public static void main(String[] args) { Phone phone = new Phone(); new Thread(() -> { try { phone.sendSMS(); } catch (Exception e) { e.printStackTrace(); } }, "Thread 1").start(); new Thread(() -> { try { phone.sendSMS(); } catch (Exception e) { e.printStackTrace(); } }, "Thread 2").start(); } } class Phone{ public synchronized void sendSMS()throws Exception{ System.out.println(Thread.currentThread().getName()+"\t -----invoked sendSMS()"); Thread.sleep(3000); sendEmail(); } public synchronized void sendEmail() throws Exception{ System.out.println(Thread.currentThread().getName()+"\t +++++invoked sendEmail()"); } }
package com.jian8.juc.lock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockDemo { public static void main(String[] args) { Mobile mobile = new Mobile(); new Thread(mobile).start(); new Thread(mobile).start(); } } class Mobile implements Runnable{ Lock lock = new ReentrantLock(); @Override public void run() { get(); } public void get() { lock.lock(); try { System.out.println(Thread.currentThread().getName()+"\t invoked get()"); set(); }finally { lock.unlock(); } } public void set(){ lock.lock(); try{ System.out.println(Thread.currentThread().getName()+"\t invoked set()"); }finally { lock.unlock(); } } }
3、獨佔鎖(寫鎖)/共享鎖(讀鎖)/互斥鎖
-
概念
-
獨佔鎖:指該鎖一次只能被一個執行緒所持有,對ReentrantLock和Synchronized而言都是獨佔鎖
-
共享鎖:只該鎖可被多個執行緒所持有
ReentrantReadWriteLock其讀鎖是共享鎖,寫鎖是獨佔鎖
-
互斥鎖:讀鎖的共享鎖可以保證併發讀是非常高效的,讀寫、寫讀、寫寫的過程是互斥的
-
-
程式碼示例
package com.jian8.juc.lock; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * 多個執行緒同時讀一個資源類沒有任何問題,所以為了滿足併發量,讀取共享資源應該可以同時進行。 * 但是 * 如果有一個執行緒象取寫共享資源來,就不應該自由其他執行緒可以對資源進行讀或寫 * 總結 * 讀讀能共存 * 讀寫不能共存 * 寫寫不能共存 */ public class ReadWriteLockDemo { public static void main(String[] args) { MyCache myCache = new MyCache(); for (int i = 1; i <= 5; i++) { final int tempInt = i; new Thread(() -> { myCache.put(tempInt + "", tempInt + ""); }, "Thread " + i).start(); } for (int i = 1; i <= 5; i++) { final int tempInt = i; new Thread(() -> { myCache.get(tempInt + ""); }, "Thread " + i).start(); } } } class MyCache { private volatile Map<String, Object> map = new HashMap<>(); private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); /** * 寫操作:原子+獨佔 * 整個過程必須是一個完整的統一體,中間不許被分割,不許被打斷 * * @param key * @param value */ public void put(String key, Object value) { rwLock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName() + "\t正在寫入:" + key); TimeUnit.MILLISECONDS.sleep(300); map.put(key, value); System.out.println(Thread.currentThread().getName() + "\t寫入完成"); } catch (Exception e) { e.printStackTrace(); } finally { rwLock.writeLock().unlock(); } } public void get(String key) { rwLock.readLock().lock(); try { System.out.println(Thread.currentThread().getName() + "\t正在讀取:" + key); TimeUnit.MILLISECONDS.sleep(300); Object result = map.get(key); System.out.println(Thread.currentThread().getName() + "\t讀取完成: " + result); } catch (Exception e) { e.printStackTrace(); } finally { rwLock.readLock().unlock(); } } public void clear() { map.clear(); } }
4、自旋鎖
-
spinlock
是指嘗試獲取鎖的執行緒不會立即阻塞,而是==採用迴圈的方式去嘗試獲取鎖==,這樣的好處是減少執行緒上下文切換的消耗,缺點是迴圈會消耗CPU
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; }
手寫自旋鎖:
package com.jian8.juc.lock; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; /** * 實現自旋鎖 * 自旋鎖好處,迴圈比較獲取知道成功位置,沒有類似wait的阻塞 * * 通過CAS操作完成自旋鎖,A執行緒先進來呼叫mylock方法自己持有鎖5秒鐘,B隨後進來發現當前有執行緒持有鎖,不是null,所以只能通過自旋等待,知道A釋放鎖後B隨後搶到 */ public class SpinLockDemo { public static void main(String[] args) { SpinLockDemo spinLockDemo = new SpinLockDemo(); new Thread(() -> { spinLockDemo.mylock(); try { TimeUnit.SECONDS.sleep(3); }catch (Exception e){ e.printStackTrace(); } spinLockDemo.myUnlock(); }, "Thread 1").start(); try { TimeUnit.SECONDS.sleep(3); }catch (Exception e){ e.printStackTrace(); } new Thread(() -> { spinLockDemo.mylock(); spinLockDemo.myUnlock(); }, "Thread 2").start(); } //原子引用執行緒 AtomicReference<Thread> atomicReference = new AtomicReference<>(); public void mylock() { Thread thread = Thread.currentThread(); System.out.println(Thread.currentThread().getName() + "\t come in"); while (!atomicReference.compareAndSet(null, thread)) { } } public void myUnlock() { Thread thread = Thread.currentThread(); atomicReference.compareAndSet(thread, null); System.out.println(Thread.currentThread().getName()+"\t invoked myunlock()"); } }