從字面意思理解,其實灰常簡單,公平就是大家買票都排隊,不公平就是有人開了超級VIP,插隊了。所以在多線程中,就存在公平鎖和非公平鎖,如何理解呢?
公平鎖:多個線程按照申請鎖的順序去獲得鎖,所有線程都在隊列裏排隊,這樣就保證了隊列中的第一個先得到鎖。
優點:所有的線程都能得到資源,不會餓死在隊列中。
缺點:吞吐量會下降很多,隊列裏面除了第一個線程,其他的線程都會阻塞,cpu喚醒阻塞線程的開銷會很大。
非公平鎖:多個線程不按照申請鎖的順序去獲得鎖,而是同時直接去嘗試獲取鎖(插隊),獲取不到(插隊失敗),再進入隊列等待(失敗則乖乖排隊),如果能獲取到(插隊成功),就直接獲取到鎖。
優點:可以減少CPU喚醒線程的開銷,整體的吞吐效率會高點,CPU也不必取喚醒所有線程,會減少喚起線程的數量。
缺點:可能導致隊列中排隊的線程一直獲取不到鎖或者長時間獲取不到鎖,活活餓死。
在Java多線程併發操作中,我們操作鎖大多時候都是基於Sync本身去實現的,而Sync本身卻是ReentrantLock本身的一個內部類,Sync本身又繼承AbstractQueuedSynchronizer,如圖:
那我們如何去實現一個公平鎖和非公平鎖呢?我們就以買票這個例子,通過ReentrantLock進行講解:
public class FairLocked implements Runnable {
private int seatNumber = 100;
/**
* 公平鎖實現 ReentrantLock構造方法中設置爲true:代表公平鎖
*
* 設置爲false:代表非公平鎖 默認也是非公平鎖
*
*/
/** private ReentrantLock lock = new ReentrantLock(true); */
/** private ReentrantLock lock = new ReentrantLock(false); */
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();
if (seatNumber > 0) {
Thread.sleep(100);
--seatNumber;
System.out.println(Thread.currentThread().getName() + "佔用1個座位,還剩餘 " + seatNumber + "個座位");
} else {
System.out.println(Thread.currentThread().getName() + ":不好意思,票賣完了!");
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
FairLocked rlbr = new FairLocked();
Thread t1 = new Thread(rlbr, "A窗口");
Thread t2 = new Thread(rlbr, "B窗口");
t1.start();
t2.start();
}
}
需要注意的是,默認情況下是非公平,想要公平鎖就得設置爲true,所以看上述的輸出就可以看出,A線程和B線程存在資源爭搶(插隊),這個就是非公平鎖,我們設置爲true看看是什麼效果:
可以看出,AB線程是非常有序的,就是進行排隊執行!