java源码分析:重入锁ReentrantLock

来源:互联网 发布:caffe 前向计算 编辑:程序博客网 时间:2024/06/05 23:05

可重入:一个线程可以连续多次获得锁

加锁

Lock.lock() 获取锁

        final void lock() {            acquire(1);        }

很简单,就是调用acqire函数,后面的参数1表示,如果获得了锁,对于另外进程释放而获取获取的,那么就锁的值就是1,否则如果是重入的值+1

public final void acquire(int arg) {        if (!tryAcquire(arg) &&            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))            selfInterrupt();    }

首先通过tryAcquire获取锁,如果没有获取,那么首先通过addWaiter把当前线程放到等待队列的最后面,并阻塞它。

而在tryAcquire中,根据是否公平锁有两个版本,先看公平锁

    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;        }

这个函数就是尝试去获取锁,返回是否获取了锁。首先获取锁的状态,看是否为0,如果是0,那么当前是没有线程占据锁的,那么判断当前线程是否在队列的第一个,如果是那么通过原子的compareAndSetState获取锁,把当前线程设置为锁的获得者,返回true。如果当前锁是被线程占领的,那么看下是不是被当前线程占了,如果是的话,把对应的持有锁的次数+acquires。 如果没有成功获取锁,那么就会调用addWaiter和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;    }

以当前的线程为信息,新建一个节点放到列表的最后面。
然后来看下acquireQueued

    final boolean acquireQueued(final Node node, int arg) {        boolean failed = true;        try {            boolean interrupted = false;            for (;;) {                final Node p = node.predecessor();                if (p == head && tryAcquire(arg)) {                    setHead(node);                    p.next = null; // help GC                    failed = false;                    return interrupted;                }                if (shouldParkAfterFailedAcquire(p, node) &&                    parkAndCheckInterrupt())                    interrupted = true;            }        } finally {            if (failed)                cancelAcquire(node);        }    }

这块代码不断循环做两件事,第一件判断当前线程是不是队列的最前面,如果是的话,那么再次尝试获取锁,成功就把当前节点设置为头并返回是否被中断过。 否则就干第二件事,调用shouldParkAfterFailedAcquire决定是否把当前进行给阻塞,如果需要那么通过parkAndCheckInterrupt把线程阻塞,阻塞成功把interrupted设置true。

我们在来看下shouldParkAfterFailedAcquire这个函数

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {        int ws = pred.waitStatus;        if (ws == Node.SIGNAL)            /*             * This node has already set status asking a release             * to signal it, so it can safely park.             */            return true;        if (ws > 0) {            /*             * Predecessor was cancelled. Skip over predecessors and             * indicate retry.             */            do {                node.prev = pred = pred.prev;            } while (pred.waitStatus > 0);            pred.next = node;        } else {            /*             * waitStatus must be 0 or PROPAGATE.  Indicate that we             * need a signal, but don't park yet.  Caller will need to             * retry to make sure it cannot acquire before parking.             */            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);        }        return false;    }

首先看当前线程的前个节点的状态,如果是SIGNAL,那么他们都没有获取锁,那就直接返回true表示进入阻塞状态

其次:把前面节点中是cancelled状态的给删除了,把当前节点作为第一个非cancelled进程的下游

否则就设置前继的状态为SIGNAL,这样下次循环可以进入1而进入阻塞

如果需要阻塞,通过下面函数完成

 private final boolean parkAndCheckInterrupt() {        LockSupport.park(this);        return Thread.interrupted();    }

阻塞,如果阻塞结束后,返回线程的中断状态。

# 参考

http://blog.csdn.net/yanyan19880509/article/details/52345422

0 0
原创粉丝点击