锁的公平与非公平
锁是给线程用的,多线程场景下为了保证线程安全,得在拿到锁之后才能干活。当多个线程竞争一个锁时,同一个时间只能有一个线程能脱颖而出的持有锁,其他线程必须等该线程释放锁后发起下一轮竞争。那么这种竞争就存在公平性问题,如果是公平的竞争,那么这些线程就得按先来后到依次得到锁。这就要求线程们先排好队,前面的线程使用完后把锁递给后面的线程,以此类推。非公平的锁是无序的,锁在被释放那一刻刚好谁运气好碰到了就给谁。
举个例子:线程甲、乙、丙依次请求锁,如果是公平的,那么就按这个次序来。比如甲先取到了锁,那么没有意外,等它释放后肯定是按先乙后丙这个顺序来取得锁;如果是非公平,甲先持有锁,释放后乙跟丙都有可能抢到锁。
从上面例子可以看到,如果竞争很激烈,锁被持有时间短,那么非公平锁能充分利用时间,公平锁反而因为线程的排队浪费了时间。反之,如果线程持有锁的时间长,那么非公平锁会被频繁请求,线程做了很多无用功,而公平锁按部就班传递锁反而减少了不必要的线程调度。内置锁(synchronized)只能是非公平的,显式锁(ReentrantLock)可以自己定义,下面看代码:
package com.wlf.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拿到。