锁是给线程用的,线程拿到锁之后才能干活。当多个线程竞争一个锁时,同一个时间只能有一个线程脱颖而出持有锁,其他线程等该线程释放锁后发起下一轮竞争。那么这种竞争就存在公平性问题,如果是公平的竞争,那么这些线程就得有序来依次得到锁,这就需要线程们请求的按先来后到排队,第一个线程使用完后把锁递给第二个线程,以此类推。非公平的锁是无序的,锁在被释放那会儿刚好谁运气好碰到了就给谁。
举个例子:线程甲、乙、丙依次请求锁,如果是公平的,那么就按这个次序来。比如乙先取到了锁,那么没有意外,等它释放后肯定是先甲后丙来取得锁;如果是非公平,甲先持有锁,乙跑过来争,发现被甲拿了就去睡觉(被挂起)了,当甲释放锁通知乙来取的过程中,刚好丙半路杀过来了,把锁拿去用了,等丙释放锁时乙刚好完全被唤醒拿到了锁。
从上面例子可以看到,如果竞争很激烈,锁被持有时间短,那么非公平锁能充分利用时间,公平锁反而因为线程的切换浪费了时间。反之,如果线程持有锁的时间长,那么非公平锁会被频繁请求,线程重试次数多,做了很多无用功,而公平锁按部就班传递锁反而减少了不必要的线程调度。内置锁(synchronized)只能是非公平的,显式锁(ReentrantLock)可以自己定义,下面看代码:
package com.wulinfeng.concurrent; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class FairAndUnfairLock { private Lock lock; // 被线程们竞争的一把锁,而且多个线程争的都是这同一把 public FairAndUnfairLock(boolean isFair) { this.lock = new ReentrantLock(isFair); } /** * 根据是否公平设置锁,线程们进入到这个方法,说明都是来争锁的,某一线程争到了,其他线程就得等 * * @param isFair */ public void fightForLock() { lock.lock(); try { System.out.println("----" + Thread.currentThread().getName() + "获得锁."); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } finally { lock.unlock(); } } public static void main(String[] args) { final FairAndUnfairLock lock = new FairAndUnfairLock(true); // 公平锁 int threadNums = 10; Thread[] threads = new Thread[threadNums]; for (int i = 0; i < threadNums; i++) { threads[i] = new Thread(new Runnable() { @Override public void run() { System.out.println("****" + Thread.currentThread().getName() + "请求锁."); lock.fightForLock(); } }); } for (int i = 0; i < threadNums; i++) { threads[i].start(); } } }
输出结果:
****Thread-0请求锁. ----Thread-0获得锁. ****Thread-2请求锁. ****Thread-4请求锁. ****Thread-6请求锁. ****Thread-8请求锁. ****Thread-1请求锁. ****Thread-3请求锁. ****Thread-5请求锁. ****Thread-7请求锁. ****Thread-9请求锁. ----Thread-2获得锁. ----Thread-4获得锁. ----Thread-6获得锁. ----Thread-8获得锁. ----Thread-1获得锁. ----Thread-3获得锁. ----Thread-5获得锁. ----Thread-7获得锁. ----Thread-9获得锁.
从上面可以看到,线程0请求锁后立即得到,而后面的线程2-4-6-8-1-3-5-7-9依次请求锁在排队,等0释放后他们还是按这个顺序得到了锁。再来看下非公平的,把true改成false,再把这段休眠的代码注掉
try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
跑一把,可以看到对同一个锁竞争后获取锁顺序是乱的:
****Thread-0请求锁. ----Thread-0获得锁. ****Thread-1请求锁. ----Thread-1获得锁. ****Thread-6请求锁. ----Thread-6获得锁. ****Thread-4请求锁. ----Thread-4获得锁. ****Thread-2请求锁. ****Thread-8请求锁. ****Thread-3请求锁. ----Thread-8获得锁. ----Thread-3获得锁. ----Thread-2获得锁. ****Thread-5请求锁. ----Thread-5获得锁. ****Thread-7请求锁. ----Thread-7获得锁. ****Thread-9请求锁. ----Thread-9获得锁.
上面锁被线程4获取后,2-8-3依次请求,4释放锁时本该被2获取,但2在被唤醒的过程中8刚好来了并取到了锁,8用完了2还是没完全醒过来,然后3又来了取走了锁,当3释放后锁才被完全醒过来的2拿到。