锁的公平与非公平

  锁是给线程用的,线程拿到锁之后才能干活。当多个线程竞争一个锁时,同一个时间只能有一个线程脱颖而出持有锁,其他线程等该线程释放锁后发起下一轮竞争。那么这种竞争就存在公平性问题,如果是公平的竞争,那么这些线程就得有序来依次得到锁,这就需要线程们请求的按先来后到排队,第一个线程使用完后把锁递给第二个线程,以此类推。非公平的锁是无序的,锁在被释放那会儿刚好谁运气好碰到了就给谁。

  举个例子:线程甲、乙、丙依次请求锁,如果是公平的,那么就按这个次序来。比如乙先取到了锁,那么没有意外,等它释放后肯定是先甲后丙来取得锁;如果是非公平,甲先持有锁,乙跑过来争,发现被甲拿了就去睡觉(被挂起)了,当甲释放锁通知乙来取的过程中,刚好丙半路杀过来了,把锁拿去用了,等丙释放锁时乙刚好完全被唤醒拿到了锁。

  从上面例子可以看到,如果竞争很激烈,锁被持有时间短,那么非公平锁能充分利用时间,公平锁反而因为线程的切换浪费了时间。反之,如果线程持有锁的时间长,那么非公平锁会被频繁请求,线程重试次数多,做了很多无用功,而公平锁按部就班传递锁反而减少了不必要的线程调度。内置锁(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拿到。

原文地址:https://www.cnblogs.com/wuxun1997/p/6980015.html