Java多執行緒 -- 公平鎖和非公平鎖

微笑面對生活發表於2018-06-17

在java的鎖機制中,公平和非公平的參考物是什麼,個人而言覺得是相對產生的結果而立,簡單的來說,如果一個執行緒組裡,能保證每個執行緒都能拿到鎖,那麼這個鎖就是公平鎖。相反,如果保證不了每個執行緒都能拿到鎖,也就是存在有執行緒餓死,那麼這個鎖就是非公平鎖。本文圍繞ReenTrantLock來講。

實現原理

那如何能保證每個執行緒都能拿到鎖呢,佇列FIFO是一個完美的解決方案,也就是先進先出,java的ReenTrantLock也就是用佇列實現的公平鎖和非公平鎖。

在公平的鎖中,如果有另一個執行緒持有鎖或者有其他執行緒在等待佇列中等待這個所,那麼新發出的請求的執行緒將被放入到佇列中。而非公平鎖上,只有當鎖被某個執行緒持有時,新發出請求的執行緒才會被放入佇列中(此時和公平鎖是一樣的)。所以,它們的差別在於非公平鎖會有更多的機會去搶佔鎖。

公平鎖:

            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }


    #hasQueuedPredecessors的實現
    public final boolean hasQueuedPredecessors() {
   
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }
複製程式碼

非公平鎖:

            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
複製程式碼

示例

公平鎖:

import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by Fant.J.
 */
public class MyFairLock {
    /**
     *     true 表示 ReentrantLock 的公平鎖
     */
    private  ReentrantLock lock = new ReentrantLock(true);

    public   void testFail(){
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() +"獲得了鎖");
        }finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) {
        MyFairLock fairLock = new MyFairLock();
        Runnable runnable = () -> {
            System.out.println(Thread.currentThread().getName()+"啟動");
            fairLock.testFail();
        };
        Thread[] threadArray = new Thread[50];
        for (int i=0; i<50; i++) {
            threadArray[i] = new Thread(runnable);
            threadArray[i].start();
        }
    }
}


複製程式碼

Thread-0啟動
獲得了鎖Thread-0獲得了鎖
Thread-1啟動
獲得了鎖Thread-1獲得了鎖
Thread-2啟動
獲得了鎖Thread-2獲得了鎖
Thread-3啟動
獲得了鎖Thread-3獲得了鎖
Thread-4啟動
獲得了鎖Thread-4獲得了鎖
Thread-5啟動
獲得了鎖Thread-5獲得了鎖
Thread-6啟動
Thread-7啟動
獲得了鎖Thread-6獲得了鎖
獲得了鎖Thread-7獲得了鎖
Thread-8啟動
Thread-9啟動
獲得了鎖Thread-8獲得了鎖
Thread-10啟動
獲得了鎖Thread-9獲得了鎖
獲得了鎖Thread-10獲得了鎖
Thread-11啟動
獲得了鎖Thread-11獲得了鎖
Thread-13啟動
獲得了鎖Thread-13獲得了鎖
Thread-14啟動
獲得了鎖Thread-14獲得了鎖
Thread-15啟動
獲得了鎖Thread-15獲得了鎖
Thread-16啟動
獲得了鎖Thread-16獲得了鎖
Thread-17啟動
獲得了鎖Thread-17獲得了鎖
Thread-18啟動
獲得了鎖Thread-18獲得了鎖
Thread-19啟動
獲得了鎖Thread-19獲得了鎖
Thread-20啟動
獲得了鎖Thread-20獲得了鎖
Thread-24啟動
獲得了鎖Thread-24獲得了鎖
Thread-22啟動
獲得了鎖Thread-22獲得了鎖
Thread-27啟動
獲得了鎖Thread-27獲得了鎖
Thread-25啟動
Thread-26啟動
獲得了鎖Thread-25獲得了鎖
獲得了鎖Thread-26獲得了鎖
Thread-29啟動
獲得了鎖Thread-29獲得了鎖
Thread-28啟動
獲得了鎖Thread-28獲得了鎖
Thread-30啟動
獲得了鎖Thread-30獲得了鎖
Thread-32啟動
獲得了鎖Thread-32獲得了鎖
Thread-31啟動
獲得了鎖Thread-31獲得了鎖
Thread-33啟動
獲得了鎖Thread-33獲得了鎖
Thread-34啟動
獲得了鎖Thread-34獲得了鎖
Thread-36啟動
獲得了鎖Thread-36獲得了鎖
Thread-35啟動
獲得了鎖Thread-35獲得了鎖
Thread-38啟動
獲得了鎖Thread-38獲得了鎖
Thread-37啟動
獲得了鎖Thread-37獲得了鎖
Thread-39啟動
獲得了鎖Thread-39獲得了鎖
Thread-42啟動
Thread-41啟動
獲得了鎖Thread-42獲得了鎖
獲得了鎖Thread-41獲得了鎖
Thread-40啟動
獲得了鎖Thread-40獲得了鎖
Thread-45啟動
獲得了鎖Thread-45獲得了鎖
Thread-44啟動
獲得了鎖Thread-44獲得了鎖
Thread-43啟動
獲得了鎖Thread-43獲得了鎖
Thread-23啟動
Thread-46啟動
Thread-21啟動
獲得了鎖Thread-23獲得了鎖
Thread-49啟動
Thread-48啟動
Thread-12啟動
獲得了鎖Thread-46獲得了鎖
Thread-47啟動
獲得了鎖Thread-21獲得了鎖
獲得了鎖Thread-49獲得了鎖
獲得了鎖Thread-48獲得了鎖
獲得了鎖Thread-12獲得了鎖
獲得了鎖Thread-47獲得了鎖
複製程式碼

可以看到,獲取鎖的執行緒順序正是執行緒啟動的順序。

非公平鎖:

/**
 * Created by Fant.J.
 */
public class MyNonfairLock {
    /**
     *     false 表示 ReentrantLock 的非公平鎖
     */
    private  ReentrantLock lock = new ReentrantLock(false);

    public  void testFail(){
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() +"獲得了鎖");
        }finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) {
        MyNonfairLock nonfairLock = new MyNonfairLock();
        Runnable runnable = () -> {
            System.out.println(Thread.currentThread().getName()+"啟動");
            nonfairLock.testFail();
        };
        Thread[] threadArray = new Thread[10];
        for (int i=0; i<10; i++) {
            threadArray[i] = new Thread(runnable);
            threadArray[i].start();
        }
    }
}

複製程式碼
Thread-1啟動
Thread-0啟動
Thread-0獲得了鎖
Thread-1獲得了鎖
Thread-8啟動
Thread-8獲得了鎖
Thread-3啟動
Thread-3獲得了鎖
Thread-4啟動
Thread-4獲得了鎖
Thread-5啟動
Thread-2啟動
Thread-9啟動
Thread-5獲得了鎖
Thread-2獲得了鎖
Thread-9獲得了鎖
Thread-6啟動
Thread-7啟動
Thread-6獲得了鎖
Thread-7獲得了鎖

複製程式碼

可以看出非公平鎖對鎖的獲取是亂序的,即有一個搶佔鎖的過程。

最後

那非公平鎖和公平鎖適合什麼場合使用呢,他們的優缺點又是什麼呢?

優缺點:

非公平鎖效能高於公平鎖效能。首先,在恢復一個被掛起的執行緒與該執行緒真正執行之間存在著嚴重的延遲。而且,非公平鎖能更充分的利用cpu的時間片,儘量的減少cpu空閒的狀態時間。

使用場景

使用場景的話呢,其實還是和他們的屬性一一相關,舉個栗子:如果業務中執行緒佔用(處理)時間要遠長於執行緒等待,那用非公平鎖其實效率並不明顯,但是用公平鎖會給業務增強很多的可控制性。

相關文章