AQS之ReentrantLock

380 阅读3分钟

码农在囧途

心里只有终点的时候,路途的风景已经丢了一半,心里只想看风景的时候,离终点又远了一半!

ReentrantLock公平锁和非公平锁

ReentrantLock是一个独占锁,基于AQS实现,如果有线程获取了锁,那么其他线程来获取该锁的时候会被阻塞,ReentrantLock有两种 方式,一种是公平锁(FairLock),一种是非公平锁(NoFairLock),ReentrantLock默认是非公平锁,下面解释一下公平锁和非公平锁。

公平锁

公平锁的意思就是任何线程想要获取锁,就必须要排队等候,就好比你去饭堂吃饭,有素质的的同学都在排队,如下,使用公平锁的好处是线程 是按照先到先处理的顺序来进行,保证所有线程都能够获取到锁,不过这样的效率就会变得很低。
img.png

非公平锁

非公平锁就是不必排队就能拿到锁,就像食堂里面,那些横行霸道的同学就不排队,直接冲到第一位去,使用非公平锁的好处就是效率比较高一点, 不过就会导致有些线程一直获取不到锁,ReentrantLock默认为非公平锁。
img.png

可通过构造函数设置ReentrantLock的公平与非公平锁,默认为公平锁

public ReentrantLock() {
    sync = new NonfairSync();
}

构造函数为true为公平锁,false为非公平锁

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

ReentrantLock原理

ReentrantLock是基于AQS来实现的,其核心是对state的处理,关于state,可见上一篇文章对AQS的讲解中有详细的讲解,我们主要来看一下公平锁和非公平锁的实现方式。

线程首先去尝试获取锁,发现state,就将state通过CAS改为1,然后返回,如果线程获取锁时发现state不为0,则证明锁已经被 其他线程持有,所以就被加入AQS队列的尾部,然后被挂起,当获取到锁的线程执行完任务后释放锁后,它会唤醒队列的队头节点,然后队头的线程再执行任务, 可以看出线程的执行是有顺序的,按照FIFO先进先出的原则。

尝试获取锁

img.png

加入AQS队列,因为锁已经被其他线程持有

img.png

如下代码将线程封装成EXCLUSIVE类型的节点加入队列尾部。

private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

对于公平锁,它主要是通过hasQueuedPredecessors()判断AQS队列中的第一个线程是不是当前线程,而非公平锁则不用判断,无论是否位于队首都能抢占到锁,为什么要判断呢,因为公平锁需要每个线程都按照顺序 执行,如果当前的线程位于队首位置,则就能获取锁,如果队列首位不是当前线程,那么就需要排队。

public final boolean hasQueuedPredecessors() {
    Node t = tail;
    Node h = head;
    Node s;
    return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}

当前线程尝试获取锁,判断是否位于AQS队首
img.png

位于队首并且线程A释放子锁,这时候线程B就直接获取到锁。
img.png

ReentrantLock使用

ReentrantLock只需要在需要同步的代码段进行加锁,需要用try来包裹代码,在finally里进行释放锁,关于它的其他方法,大家可以自行​去看。​

public class ReentrantLockTest {
    private final ReentrantLock lock = new ReentrantLock();
    public void add(){
        lock.lock();
        try {
            System.out.println("get data");
        }finally {
            lock.unlock();
        }
    }
}

今天的分享就到这里,感谢你的观看,下期间。