java并发编程

浅析ReentrantLock可重入锁

2017-09-15  本文已影响102人  薛云龙

Lock锁接口在JAVA SE5之后,出现在并发包中.它提供了与synchronized关键字一样的同步功能.只是在使用时需要显式地获取和释放锁,缺点就是缺少像synchronized那样隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性,可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。

可重入锁和不可重入锁

ReentrantLock基本使用

ReentrantLock对资源进行加锁,同一时刻只会有一个线程能够占有锁.当前锁被线程占有时,其他线程会进入挂起状态,直到该锁被释放,其他挂起的线程会被唤醒并开始新的竞争.

public class ReentrantLockExample {
    private ReentrantLock reentrantLock = new ReentrantLock();

    private void test(){
        reentrantLock.lock();
        System.out.println("进行原子操作");
        try {
            Thread.sleep(3000l);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        ReentrantLockExample reentrantLockExample = new ReentrantLockExample();
        for (int i = 0; i < 5; i++) {
            executorService.submit(new Thread(reentrantLockExample::test));
        }
    }
}

通过控制台的输出信息可知:每隔三秒输出一次信息.

ReentrantLock很大程度的依赖了抽象类AbstractQueuedSynchronizer,这里需要先对AbstractQueuedSynchronizer进行了解.看我下面的这篇文章

ReentrantLock又有公平锁和非公平锁之分,所以可以看到在源码中有两个锁的实现


公平锁:每个线程的资源竞争是公平的.按照自身线程调用lock方法的顺序来获取锁,即先到先得.
非公平锁:每个线程的资源竞争是顺序不定,谁的优先级高,那么谁就会先获得锁.

ReentrantLock几个重要方法的剖析

/**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }
1.获取一把锁,如果该锁没有被其他线程持有,则立即返回,并将锁计数器加一;
2.如果当前线程已经持有该锁,则将锁计数器加一,并立即返回.
3.如果该锁被其他线程持有,那么当前线程的目的将会无效并进入休眠状态.直到锁计数器的值为1.
public void lock() {
        sync.lock();
    }

这里先看一下,公平锁的lock方法的实现.

FairSync:默认调用AbstractQueuedSynchronizer的acquire()方法.
final void lock() {
            acquire(1);
        }
AbstractQueuedSynchronizer:
public final void acquire(int arg) {
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

非公平锁的lock方法实现:

NonfairSync:
final void lock() {
        //通过CAS操作,将AQS中的stateOffset从0改为1.
        if (compareAndSetState(0, 1))
             //将当前线程设为独享线程
              setExclusiveOwnerThread(Thread.currentThread());
        else
             //否则,再次请求同步状态.一般参数为0是释放锁,参数为1是获取锁
              acquire(1);
}
AbstractQueuedSynchronizer:其中tryAcquire()是由具体的实现类实现的.
public final void acquire(int arg) {
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}
这里分为两步进行分析:
1.NonfairSync:执行tryAcquire方法
protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        //获取当前AQS的状态
        int c = getState();
        if (c == 0) {//同步状态为0时,执行CAS操作
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }//当前线程已经获取到锁,因为当前是重入锁,则state+1,并返回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;
}
2.NonfairSync:tryAcquire方法返回false之后,会执行acquireQueued方法.
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;
    }

因为NonfairSync是非公平锁,所以对于新来的线程和同步队列的线程,都能调用这个方法来获取到锁.
由于实现过于复杂,这里总结来说:当多个线程赖竞争锁时,只会有一个线程能够获取到锁,那么其他线程将会通过CAS操作(保证数据的一致性)来将当前线程添加到同步队列中,当所有线程进入到同步队列之后,就会进入自旋状态(死循环判断当前线程所在节点的前驱节点是否为head节点)去尝试获取同步状态.

private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

如果检测到线程的中断,将会直接抛出异常.

参考文章

上一篇下一篇

猜你喜欢

热点阅读