likes
comments
collection
share

为什么要设计非公平锁?

作者站长头像
站长
· 阅读数 38

为什么要非公平锁?

背景

公平:排队

非公平:在合适时机插队

非公平还是 ReentrantLock 的默认策略,排队时间不浪费了?

场景

来看这种场景

假如A持有一把锁,B请求这把锁,这时候B被挂起进入阻塞,A释放锁的时候,C来了进行请求,A就把锁给了C,因为唤醒B是需要很大开销的,很可能在B唤醒之前C就已经拿到这把锁执行完任务释放了这把锁,那就是双赢,C的执行速度相比于B被唤醒是很快的,这样设计就提高了整体运行效率

那假如公平呢?

那就进入等待队列等待,依次获得锁

不公平呢?

为什么要设计非公平锁?

T1释放锁时,T5没排队直接请求,非公平策略:从整体效率考虑,这把锁会给T5

实验

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
​
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();
        }
    }
}

false:不公平

这里只给出非公平锁结果

Thread 0: Going to print a job
Thread 0: PrintQueue: Printing a Job during 8 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 9 seconds
Thread 0: The document has been printed
Thread 1: PrintQueue: Printing a Job during 8 seconds
Thread 1: PrintQueue: Printing a Job during 2 seconds
Thread 1: The document has been printed
Thread 2: PrintQueue: Printing a Job during 7 seconds
Thread 2: PrintQueue: Printing a Job during 8 seconds
...

本应是1获得锁但此时被0插队,由此可见0释放锁又获得了锁

优缺点

为什么要设计非公平锁?

公平锁、非公平锁源码分析

public class ReentrantLock implements Lock, java.io.Serializable {
    private final Sync sync;
    abstract static class Sync extends AbstractQueuedSynchronizer {...}
    static final class FairSync extends Sync {...}
    static final class NonfairSync extends Sync{...}
}

公平锁和非公平锁加锁方法源码

公平锁

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

非公平锁

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

由此可见公平锁和非公平锁lock方法唯一区别就是公平锁获取锁时多了一个限制:hasQueuedPredecessors,等待队列中是否有线程在排队,公平锁:一旦有线程在排队,当前线程就不再尝试获取锁,对于非公平锁:无论有没有都尝试获取一下锁,获取不到再去排队

特例:tryLock方法

上源码

public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
}

意思就是一旦有线程释放锁,那么正在tryLock的线程就能获取到锁,即便设置的是公平锁模式,即便他前面有等待的线程,它也可以插队