并发八: ReentrantLock实现分析

显式锁

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

推荐阅读更多精彩内容

  • pdf下载地址:Java面试宝典 第一章内容介绍 20 第二章JavaSE基础 21 一、Java面向对象 21 ...
    王震阳阅读 77,116评论 25 508
  • 线程安全的Set J.U.C中实现Set接口的并发容器有CopyOnWriteArraySet和Concurren...
    wangjie2016阅读 1,010评论 0 5
  • ReentrantLock 介绍 一个可重入的互斥锁,它具有与使用{synchronized}方法和语句访问的隐式...
    tomas家的小拨浪鼓阅读 1,123评论 0 2
  • 一、前言 借用Java并发编程实践中的话"编写正确的程序并不容易,而编写正常的并发程序就更难了",相比于顺序执行的...
    云胜886阅读 123评论 0 2
  • 我与简书的相遇要属偶遇了 。今天与闺蜜聊天中闲聊到一个关于亲子征文的比赛,因为我有个三个月的宝宝符合征文对象的条件...
    kk嘴角上扬阅读 70评论 5 4