Java并发--重入锁ReentrantLock公平性与非公平性的区别

前言

重入锁ReentrantLock有两种方式获取锁,一种是公平性,一种是非公平性。如果在绝对时间上,先请求获取锁的线程一定会先获取到锁,那么这个锁就是公平的,反之,这个锁就是不公平的。公平的获取锁,也就是等待时间最长的线程最优先获取锁,也可以说锁获取是顺序的。那重入锁ReentrantLock如何实现公平锁,以及非公平锁,它们的区别又是什么?

正文

重入锁ReentrantLock默认使用非公平获取锁,同时提供构造函数,能够控制锁是否是公平。

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

公平锁使用FairSync同步组件,非公平锁使用NonfairSync同步组件。FairSync与NonfairSync均是ReentrantLock的内部类,又共同继承了ReentrantLock的一个内部类Sync。FairSync与NonfairSync不同的地方在于重写Sync的lock()方法与tryAcquire()方法。我们先看一下重入锁ReentrantLock默认的非公平锁如何获取锁。

非公平获取锁

非公平性获取锁调用NonfairSync的lock()方法

        final void lock() {
            // 如果当前同步状态为O,表示锁未被任何线程获取,CAS设置同步状态为1
            if (compareAndSetState(0, 1))
                // 设置当前线程为获取锁的线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
                // 如果锁已经被某线程获取,尝试获取锁
                acquire(1);
        }

NonfairSync的lock()方法会调用同步器acquire方法,最终会调用NonfairSync的nonfairTryAcquire方法来获取同步状态。

        final boolean nonfairTryAcquire(int acquires) {
            // 获取当前线程
            final Thread current = Thread.currentThread();
            // 获取同步状态
            int c = getState();
            // 同步状态为0,表示没有线程获取锁
            if (c == 0) {
                // CAS获取同步状态
                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;
        }

nonfairTryAcquire会判断同步状态是否为0,如果同步状态是0,说明目前没有任何线程获取到锁,设置获取锁的线程为当前线程并返回true,表示同步状态成功。当同步状态不为0时,判断当前线程是否为获取锁的线程,如果是获取锁的线程再次请求,则将同步状态的值进行增加并返回true,表示同步状态成功。

在非公平性获取锁中,只要CAS设置同步状态成功,则表示当前线程获取了锁。那公平性是如何获取锁?

公平性获取锁

公平性获取锁调用FairSync的lock()方法

        final void lock() {
            // 获取锁
            acquire(1);
        }

FairSync的lock()方法调用同步器acquire方法,最终会调用FairSync的tryAcquire方法来获取同步状态。

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            // 同步状态为0,表示没有线程获取锁
            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;
        }
    }

我们把该方法与非公平获取锁时调用的nonfairTryAcquire相比较,唯一不同的位置在于当同步状态为0时,增加hasQueuedPredecessors判断,而该方法判断在同步队列中当前节点是否有前驱节点,如果该方法返回true,表示有线程比当前线程更早的请求获取锁,因此需要等待前驱线程获取并释放锁后才能继续获取锁。

注:hasQueuedPredecessors方法为同步器AbstractQueuedSynchronizer中的方法,锁的语义均由同步器AbstractQueuedSynchronizer实现。

    public final boolean hasQueuedPredecessors() {
        // 同步队列尾节点
        Node t = tail; 
        // 同步队列头节点
        Node h = head;
        Node s;
        // 判断头节点的后继节点的线程是否为当前线程,即判断当前节点是否有前驱节点
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

重入锁ReentrantLock在获取锁时通过调用hasQueuedPredecessors方法来实现公平锁的语义。

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