1. 程式人生 > 其它 >18. JUC談談你對公平鎖/非公平鎖/可重入鎖/遞迴鎖/自旋鎖的理解

18. JUC談談你對公平鎖/非公平鎖/可重入鎖/遞迴鎖/自旋鎖的理解

技術標籤:JUC多執行緒java併發程式設計

公平和非公平鎖

  • 公平鎖是指多個執行緒按照申請鎖的順序來獲取鎖類似排隊,先來後到

  • 非公平鎖是指,在高併發的情況下,有可能後面的執行緒比前面的執行緒先得到鎖,多個執行緒獲取鎖的順序並不按照申請鎖的順序,這就可能造成優先順序反轉或者飢餓現象

兩者的區別

併發包ReentrantLock的建立,可以指定建構函式的boolean型別來得到公平鎖或者非公平鎖,預設是非公平鎖

公平鎖,就是很公平,在併發環境下,每個執行緒在獲取鎖時會檢視此鎖維護的等待佇列,如果為空,或者當前執行緒是等待佇列的第一個,

就佔有鎖,否則就會加入到等待佇列中,以後會按照FIFO的規則從佇列中取到自己

非公平鎖,比較粗魯,上來就嘗試直接佔有鎖,如果嘗試失敗,就再採用類似公平鎖那種方式。

題外話

  • 對於ReentrantLock而言,通過建構函式指定該鎖是否為公平鎖,預設是false,非公平鎖,非公平鎖的優點在於吞吐量比公平鎖大.

  • synchronized也是一種非公平鎖

可重入鎖

可重入鎖也叫做遞迴鎖,指的是同一執行緒,外層函式獲得鎖之後,內層遞迴函式仍然能獲取該鎖的程式碼。

ReentrantLock/synchronized就是典型的可重入鎖,可重入鎖最大的作用就是避免死鎖

使用synchronized實現

public class ReentrantLockDemo{
	public
static void main(String[] args){ Phone p = new Phone(); new Thread(() -> { p.sendEms(); },"t1").start(); new Thread(() -> { p.sendEms(); },"t2").start(); } } class Phone{ public synchronized void sendEms(){ System.out.println(Thread.currentThread(
).getName() + "\tsendEms"); sendEmail(); } public synchronized void sendEmail(){ System.out.println(Thread.currentThread().getName() + "\tsendEmail"); } }

使用RenentrantLock實現

public class ReentrantLockDemo{
	public static void main(String[] args){
		Phone p = new Phone();
		Thread t3 = new Thread(p,"t3");
		Thread t4 = new Thread(p,"t4");
		t3.start();
		t4.start();
	}
}

class Phone implements Runnable{
	Lock l = new ReentrantLock();
	
	@Override
	public void run(){
		get();
	}
	
	public void get(){
		l.lock();
		try{
			System.out.println(Thread.currentThread().getName() + "\tget");
            set();
		}finally{
			l.unlock();
		}
	}
	
	public void set(){
		l.lock();
		try{
			System.out.println(Thread.currentThread().getName() + "\tset");
		}finally{
			l.unlock();
		}
	}	
}

自旋鎖

是指嘗試獲取鎖的執行緒不會立即阻塞,而是採用迴圈的方式獲取鎖,這樣的好處是減少執行緒上下文切換的消耗,缺點是迴圈會消耗CPU

image-20210106210021643.png

手寫一個自旋鎖

public class SpinLockDemo {
    public static void main(String[] args) {
        SpinLock s = new SpinLock();
        new Thread(() -> {
            s.myLock();
            // 模擬A執行緒佔用鎖5秒鐘
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            s.myUnlock();
        }, "A").start();

        // 暫停1秒鐘,保證A執行緒先執行
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            // B執行緒在A執行緒持有鎖的這5秒期間,會一直迴圈訪問comepareAndSet,
            // 直到A執行緒解鎖之後,B執行緒才會獲得鎖,然後1秒之後B執行緒解鎖
            s.myLock();
            // 暫停1秒鐘,使結果清晰
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            s.myUnlock();
        }, "B").start();
    }
}

class SpinLock {
    AtomicReference<Thread> ar = new AtomicReference<>();

    public void myLock() {
        Thread thread = Thread.currentThread();
        // 如果是第一個執行緒進來,則為其設定上鎖,結果返回true,退出迴圈
        // 如果不是第一個執行緒進來,則不會為其上鎖,結果會返回false,一直迴圈
        System.out.println(thread.getName() + "\t進來了");
        while (!ar.compareAndSet(null, thread)) {

        }
        System.out.println(thread.getName() + "\t拿到鎖了");
    }

    public void myUnlock() {
        Thread thread = Thread.currentThread();
        // 當前執行緒使用完鎖之後,為其解鎖
        ar.compareAndSet(thread, null);
        System.out.println(thread.getName() + "\t解鎖了");
    }

}