多线程(3)——ReentrantLock的公平锁与非公平锁

什么是公平锁和非公平锁

  • 公平锁(Fair):加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得
  • 非公平锁(Nonfair):加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待

怎样设置公平锁和非公平锁

  • 先看一下源码
//定义成final型的成员变量,在构造方法中进行初始化 
private final Sync sync;
//无参数默认非公平锁
public ReentrantLock() {
    sync = new NonfairSync();
}
//根据参数初始化为公平锁或者非公平锁 
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
  • 由上面的源码可以看出初始化ReentrantLock时无参为非公平锁(默认的方式),传参数true为公平锁

代码示例

public class ReentrantLockTest {
    private static Lock fairLock = new ReentrantLock2(true);
    private static Lock unfairLock = new ReentrantLock2();

    @Test
    public void fair() {
        System.out.println("fair version");
        try {
            for (int i = 0; i < 5; i++) {
                Thread thread = new Thread(new Job(fairLock)) {
                    public String toString() {
                        return getName();
                    }
                };
                thread.setName("" + i);
                thread.start();
            }
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void unfair() {
        System.out.println("unfair version");
        try {
            for (int i = 0; i < 5; i++) {
                Thread thread = new Thread(new Job(unfairLock)) {
                    public String toString() {
                        return getName();
                    }
                };
                thread.setName("" + i);
                thread.start();
            }
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static class Job implements Runnable {
        private Lock lock;

        public Job(Lock lock) {
            this.lock = lock;
        }

        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                lock.lock();
                try {
                    System.out.println("Lock by:"
                            + Thread.currentThread().getName() + " and "
                            + ((ReentrantLock2) lock).getQueuedThreads()
                            + " waits.");
                    Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    // 重写ReentrantLock中的getQueuedThreads()方法,获取等待的线程队列
    private static class ReentrantLock2 extends ReentrantLock {

        private static final long serialVersionUID = 1773716895097002072L;

        public ReentrantLock2() {}

        public ReentrantLock2(boolean fair) {
            super(fair);
        }

        public Collection getQueuedThreads() {
            return super.getQueuedThreads();
        }
    }
}
  • 运行结果:从下面的运行结果可以看出

    unfair version
    Lock by:2 and [] waits.
    Lock by:2 and [4, 0, 1, 3] waits.
    Lock by:2 and [4, 0, 1, 3] waits.
    Lock by:2 and [4, 0, 1, 3] waits.
    Lock by:2 and [4, 0, 1, 3] waits.
    Lock by:3 and [4, 0, 1] waits.
    Lock by:3 and [4, 0, 1] waits.
    Lock by:3 and [4, 0, 1] waits.
    Lock by:3 and [4, 0, 1] waits.
    Lock by:3 and [4, 0, 1] waits.
    Lock by:1 and [4, 0] waits.
    Lock by:1 and [4, 0] waits.
    Lock by:1 and [4, 0] waits.
    Lock by:1 and [4, 0] waits.
    Lock by:1 and [4, 0] waits.
    Lock by:0 and [4] waits.
    Lock by:0 and [4] waits.
    Lock by:0 and [4] waits.
    Lock by:0 and [4] waits.
    Lock by:4 and [0] waits.
    Lock by:4 and [0] waits.
    Lock by:4 and [0] waits.
    Lock by:4 and [0] waits.
    Lock by:4 and [0] waits.
    Lock by:0 and [] waits.
    fair version
    Lock by:0 and [] waits.
    Lock by:0 and [] waits.
    Lock by:1 and [0, 4, 3, 2] waits.
    Lock by:2 and [1, 0, 4, 3] waits.
    Lock by:3 and [2, 1, 0, 4] waits.
    Lock by:4 and [3, 2, 1, 0] waits.
    Lock by:0 and [4, 3, 2, 1] waits.
    Lock by:1 and [0, 4, 3, 2] waits.
    Lock by:2 and [1, 0, 4, 3] waits.
    Lock by:3 and [2, 1, 0, 4] waits.
    Lock by:4 and [3, 2, 1, 0] waits.
    Lock by:0 and [4, 3, 2, 1] waits.
    Lock by:1 and [0, 4, 3, 2] waits.
    Lock by:2 and [1, 0, 4, 3] waits.
    Lock by:3 and [2, 1, 0, 4] waits.
    Lock by:4 and [3, 2, 1, 0] waits.
    Lock by:0 and [4, 3, 2, 1] waits.
    Lock by:1 and [4, 3, 2] waits.
    Lock by:2 and [1, 4, 3] waits.
    Lock by:3 and [2, 1, 4] waits.
    Lock by:4 and [3, 2, 1] waits.
    Lock by:1 and [4, 3, 2] waits.
    Lock by:2 and [4, 3] waits.
    Lock by:3 and [4] waits.
    Lock by:4 and [] waits.

  • 可以看出在非公平模式下线程获取锁“插队”非常严重,当前获取锁的线程不受sync队列中等待的线程影响就获取了锁,而公平锁就很“公平”,按照sync队列中的顺序来获取锁

  • 对于公平模式下锁的获取,每次都由sync队列中等待最长的线程(链表的第一个,sync队列是由尾部结点添加,当前输出的sync队列是逆序输出)获取锁

公平锁与非公平锁的吞吐量

  • 线程在非公平锁模式下的吞吐量比公平锁模式下高,原因如下:
    非公平锁模式下,当线程释放锁之后,快速的通过Fast通道(下面代码片段中的tryAcquire()方法)再次获取锁,就算当前sync队列中有排队等待的线程也会被忽略。这种模式,可以保证进入和退出锁的吞吐量,但是sync队列中过早排队的线程会一直处于阻塞状态,造成“饥饿”场景。而公平性锁,就是在调用中顾及当前sync队列中的等待节点(废弃了Fast通道),也就是任意请求都需要按照sync队列中既有的顺序进行,先到先得。这样很好的确保了公平性,但是可以从结果中看到,吞吐量就没有非公平的锁高了。
    多线程(3)——ReentrantLock的公平锁与非公平锁_第1张图片
public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

源码地址:https://github.com/jiangxlGit/steady/tree/master/steady

你可能感兴趣的:(多线程)