美文网首页Java 杂谈Java学习笔记程序员
并发八: ReentrantLock实现分析

并发八: ReentrantLock实现分析

作者: wangjie2016 | 来源:发表于2018-04-14 15:23 被阅读40次

    显式锁

    synchronized被称为内置锁,线程只要进入临界区就会自动获取锁,执行完同步代码后会自动释放锁。
    J.U.C中的ReentrantLock、ReentrantReadWriteLock需要手动的进行加锁和解锁,与synchronized相呼应通常称之为显式锁。

    ReentrantLock

    顾名思义支持重入,和synchronized一样,线程可以重复获取同一个锁。同时ReentrantLock还支持公平性设定,可以设置其为公平锁或者是非公平锁。

    public class ReentrantLock implements Lock, java.io.Serializable {
        /** 队列同步器器 AQS实现 */
        abstract static class Sync extends AbstractQueuedSynhronizer {... ...}  
        /** 非公平同步器 */
        static final class NonfairSync extends Sync{... ...}
        /** 公平同步器 */
        static final class FairSync extends Sync{... ...}
        /** 同步器实例 */
        private final Sync sync;
        /** 构造 */
        public ReentrantLock() {
            sync = new NonfairSync();
        }
        /** 构造 */
        public ReentrantLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
        }
        ... ...
    }
    

    ReentrantLock使用队列同步器AQS作为基础同步工具,NonfairSync为非公平锁,FairSync为公平锁同步器,默认是非公平锁,可以通过fair设置为公平锁。

    非公平锁加锁:

    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;// 重入时直接将状态值+1
            if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
    

    s1:State==0为首次获取锁,CAS将同步状态设置为1,设置成功说明获取到锁,将持有锁的线程设置为当前线程,返回。
    s2:State>0并且持有线程为当前线程,将State设置为State+1,这里不存在线程争用,所以直接设置即可。

    公平锁加锁方法:

    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;// 重入时直接将状态值+1
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
        }
        return false;
    }
    

    hasQueuedPredecessors()方法判断AQS同步队列中是否有等待的节点,如果有则放弃获取锁,这里就是体现公平锁FIFO特性的地方,让等待最久的节点优先获取锁。

    解锁方法:

    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;//释放时将状态值-1
        //必须为持有线程才能释放锁
        if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
        boolean free = false;
        if (c == 0) { //重入获取的锁 已释放完毕
        free = true;
        setExclusiveOwnerThread(null);
        }
        setState(c);// 不存在线程争用
        return free;
    }
    

    非公平锁和公平锁的释放都调用了父类Sync的tryRelease方法。
    如果锁完全释放即c == 0,会唤醒在同步队列中等待的后继节点,锁未完全释放不会唤醒后继节点。
    于重入锁,加锁几次就必须要进行同样次数的释放锁,才能完成解锁。

    小结

    1:synchronized隐式的加锁解锁,ReentrantLock需要手动的加锁解锁
    2:在同步代码中出现异常时,synchronized会自动释锁,ReentrantLock必须在finally中释放。
    3:ReentrantLock可以非阻塞的获取锁(tryLock),synchronized不行。
    4:ReentrantLock可以响应中断(lockInterruptibly),synchronized不行。
    5:ReentrantLock具有超时机制(tryLock(timeout,unit)),synchronized不行。
    6:ReentrantLock支持公平锁和非公锁的选择,而synchronized只能是非公平锁。

    码字不易,转载请保留原文连接https://www.jianshu.com/p/79cbb3ab8af2

    相关文章

      网友评论

        本文标题:并发八: ReentrantLock实现分析

        本文链接:https://www.haomeiwen.com/subject/buczhftx.html