以上还是刚刚的场景,狗哥继续给你们画几个图理解下公平锁。
1、狗哥今天起得比较早(人长得帅,还这么努力)。来到食堂就 CAS 判断 state 是不是 = 0,是就修改为 1,,一然后发现自己居然排第一,最后把自己设置为加锁线程,成功买早饭。
2、小钊这比昨晚帮妹子修电脑起得比较晚,来到饭堂先判断下 state 判断状态。发现 = 1,有人占用。只能灰溜溜的去排队。
3、过段时间,狗哥买完早饭,将 state 设置为 0,并且把持有锁线程设置为 null,然后去唤醒队首的小钊。
(在小钊还未醒的时刻)另一位绿藻头小民,昨晚看语气助词片看的比较晚。来了就判断 state == 0,想插队。但是公平锁规定必须队首获取锁,他发现自己不是队首,没法获取锁很尴尬。
4、终于,小钊醒了。判断 state == 0,修改为 1。此时不能忘记还要看看自己是不是队首。发现是,最后把持有锁线程修改为自己,开心的买到了早餐。
看到这里,相信大家也彻底理解了吧?公平锁的缺点就是必须队首线程获取锁。如上例子小民都 CAS 了一遍,但因为不是队首,还是得阻塞。增加了 CPU 负担。
一个特例
针对 tryLock () 方法,它不遵守设定的公平原则。
❝例如,当有线程执行 tryLock () 方法的时候,一旦有线程释放了锁,那么这个正在 tryLock 的线程就能获取到锁,即使设置的是公平锁模式,即使在它之前已经有其他正在等待队列中等待的线程,简单地说就是 tryLock 可以插队。
❞
看它的源码就会发现:
public boolean tryLock() { return sync.nonfairTryAcquire(1); }
这里调用的就是 nonfairTryAcquire (),表明了是不公平的,和锁本身是否是公平锁无关。
测试代码
/** * 描述:演示公平锁,分别展示公平和不公平的情况,非公平锁会让现在持有锁的线程优先再次获取到锁。代码借鉴自Java并发编程实战手册2.7。 */ public class FairAndUnfair { public static void main(String args[]) { PrintQueue printQueue = new PrintQueue(); Thread thread[] = new Thread[10]; for (int i = 0; i < 10; i++) { thread[i] = new Thread(new Job(printQueue), "Thread " + i); } for (int i = 0; i < 10; i++) { thread[i].start(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Job implements Runnable { private PrintQueue printQueue; public Job(PrintQueue printQueue) { this.printQueue = printQueue; } @Override public void run() { System.out.printf("%s: Going to print a job\n", Thread.currentThread().getName()); printQueue.printJob(new Object()); System.out.printf("%s: The document has been printed\n", Thread.currentThread().getName()); } } class PrintQueue { private final Lock queueLock = new ReentrantLock(false); public void printJob(Object document) { queueLock.lock(); try { Long duration = (long)(Math.random() * 10000); System.out.printf("%s: PrintQueue: Printing a Job during %d seconds\n", Thread.currentThread().getName(), (duration / 1000)); Thread.sleep(duration); } catch (InterruptedException e) { e.printStackTrace(); } finally { queueLock.unlock(); } queueLock.lock(); try { Long duration = (long)(Math.random() * 10000); System.out.printf("%s: PrintQueue: Printing a Job during %d seconds\n", Thread.currentThread().getName(), (duration / 1000)); Thread.sleep(duration); } catch (InterruptedException e) { e.printStackTrace(); } finally { queueLock.unlock(); } } }
我们可以通过改变 new ReentrantLock (false) 中的参数来设置公平 / 非公平锁。以上代码在公平的情况下的输出:
Thread 0: Going to print a job Thread 0: PrintQueue: Printing a Job during 5 seconds Thread 1: Going to print a job Thread 2: Going to print a job Thread 3: Going to print a job Thread 4: Going to print a job Thread 5: Going to print a job Thread 6: Going to print a job Thread 7: Going to print a job Thread 8: Going to print a job Thread 9: Going to print a job Thread 1: PrintQueue: Printing a Job during 3 seconds Thread 2: PrintQueue: Printing a Job during 4 seconds Thread 3: PrintQueue: Printing a Job during 3 seconds Thread 4: PrintQueue: Printing a Job during 9 seconds Thread 5: PrintQueue: Printing a Job during 5 seconds Thread 6: PrintQueue: Printing a Job during 7 seconds Thread 7: PrintQueue: Printing a Job during 3 seconds Thread 8: PrintQueue: Printing a Job during 9 seconds Thread 9: PrintQueue: Printing a Job during 5 seconds Thread 0: PrintQueue: Printing a Job during 8 seconds Thread 0: The document has been printed Thread 1: PrintQueue: Printing a Job during 1 seconds Thread 1: The document has been printed Thread 2: PrintQueue: Printing a Job during 8 seconds Thread 2: The document has been printed Thread 3: PrintQueue: Printing a Job during 2 seconds Thread 3: The document has been printed Thread 4: PrintQueue: Printing a Job during 0 seconds Thread 4: The document has been printed Thread 5: PrintQueue: Printing a Job during 7 seconds Thread 5: The document has been printed Thread 6: PrintQueue: Printing a Job during 3 seconds Thread 6: The document has been printed Thread 7: PrintQueue: Printing a Job during 9 seconds Thread 7: The document has been printed Thread 8: PrintQueue: Printing a Job during 5 seconds Thread 8: The document has been printed Thread 9: PrintQueue: Printing a Job during 9 seconds Thread 9: The document has been printed
可以看出,线程直接获取锁的顺序是完全公平的,先到先得。而以上代码在非公平的情况下的输出是这样的:
Thread 0: Going to print a job Thread 0: PrintQueue: Printing a Job during 6 seconds Thread 1: Going to print a job Thread 2: Going to print a job Thread 3: Going to print a job Thread 4: Going to print a job Thread 5: Going to print a job Thread 6: Going to print a job Thread 7: Going to print a job Thread 8: Going to print a job Thread 9: Going to print a job Thread 0: PrintQueue: Printing a Job during 8 seconds Thread 0: The document has been printed Thread 1: PrintQueue: Printing a Job during 9 seconds Thread 1: PrintQueue: Printing a Job during 8 seconds Thread 1: The document has been printed Thread 2: PrintQueue: Printing a Job during 6 seconds Thread 2: PrintQueue: Printing a Job during 4 seconds Thread 2: The document has been printed Thread 3: PrintQueue: Printing a Job during 9 seconds Thread 3: PrintQueue: Printing a Job during 8 seconds Thread 3: The document has been printed Thread 4: PrintQueue: Printing a Job during 4 seconds Thread 4: PrintQueue: Printing a Job during 2 seconds Thread 4: The document has been printed Thread 5: PrintQueue: Printing a Job during 2 seconds Thread 5: PrintQueue: Printing a Job during 5 seconds Thread 5: The document has been printed Thread 6: PrintQueue: Printing a Job during 2 seconds Thread 6: PrintQueue: Printing a Job during 6 seconds Thread 6: The document has been printed Thread 7: PrintQueue: Printing a Job during 6 seconds Thread 7: PrintQueue: Printing a Job during 4 seconds Thread 7: The document has been printed Thread 8: PrintQueue: Printing a Job during 3 seconds Thread 8: PrintQueue: Printing a Job during 6 seconds Thread 8: The document has been printed Thread 9: PrintQueue: Printing a Job during 3 seconds Thread 9: PrintQueue: Printing a Job during 5 seconds Thread 9: The document has been printed