ReentrantLock可重入锁(一)获取锁

预备知识

锁一般用于控制多线程并发访问同一资源,只有获得锁的线程才可以访问,保证每次只会被一个线程访问。

两种锁:

非阻塞锁spin lock(自旋锁)等待锁的线程,会一直重试获得锁,不会阻塞,也是自旋锁名字的由来用于锁时间非常短的情况,线程状态变为阻塞态的成本高于自旋
阻塞锁mutex lock(互斥锁)等待锁的线程会依次进入队列,线程还有可能变为阻塞态,等待被唤醒用于锁时间较长的情况

锁的实现一般需要用到cpu的原子性指令来实现,如:lock指令前缀。

原子性指令:汇编指令不是cpu执行的最小单元,一条汇编指令(如:add ax,1)cpu会分为若干小的步骤去执行,cpu也不是一条指令接一条指令去执行的,会同时处理多条指令,所以汇编指令执行过程不是原子性的是可分的。原子性指令就是要保证指令执行时不可分,并发执行同一原子指令会变成顺序执行,是cpu级别的并发控制,cpu性能会有一些损耗。

ReentrantLock是利用了cas实现的,cas可以原子性修改变量值的。


正文

一、类继承关系

Lock                      锁接口
        -ReentrantLock   可重入锁
AbstractOwnableSynchronizer      拥有者同步器
        -AbstractQueuedSynchronizer    排队同步器
                -ReentrantLock.Sync                 同步器     
                        -ReentrantLock.NonfairSync   非公平同步器

二、数据结构

ReentrantLock
  • private final Sync sync;
AbstractOwnableSynchronizer   拥有者同步器
  • private transient Thread exclusiveOwnerThread;  //获得锁的线程, 获得线程把自己保存在其中
AbstractQueuedSynchronizer   排队同步器, 这是核心类,实现了
  • private transient volatile Node head;  //队列头
  • private transient volatile Node tail;  //队列尾
  • private volatile int state;  //锁的状态, 默认是0, 锁空闲, 获得锁时改为1, 重入锁一次加1
AbstractQueuedSynchronizer. Node  队列节点,  AbstractQueuedSynchronizer的内部类
  • volatile int waitStatus;  //等待状态, 0:默认状态,-1:后驱节点需要被unpark
  • volatile Node prev;  //后驱节点
  • volatile Node next;  //前驱节点
  • volatile Thread thread;
  • Node nextWaiter;
ReentrantLock是一层包装,实现都是在同步器中。

三、获取锁调用过程

获取锁的流程图

lock方法:先用cas指令修改锁状态,如果不成功,调用acquire获取锁方法

//ReentrantLock构造方法
public ReentrantLock() {
    sync = new NonfairSync();  //默认使用非公平锁
}

//ReentrantLock的lock方法, sync默认是非公平锁
public void lock() {
    sync.lock();
}

//NonfairSync的lock方法
final void lock() {
    if (compareAndSetState(0, 1))  //cas指令修改锁状态status,从0改为1
        setExclusiveOwnerThread(Thread.currentThread());  //status修改成功, 设置锁拥有者线程为当前线程
    else
        acquire(1);  //获取锁, 传入的参数1, 用于修改锁状态status, 第一次获得锁status改为1
}

 acquire方法:先尝试获取锁,如果不成功,入队列在队列中获取锁

//AbstractQueuedSynchronizer的acquire方法
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&  //尝试获得锁, 若成功就return了, 若失败加入队列等待
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))  //加入队列等待, 然后在队列中获取锁
        selfInterrupt();
}

 tryAcquire方法:尝试获取锁

//NonfairSync的tryAcquire方法, 尝试获取锁
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

//Sync的nonfairTryAcquire方法
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {  //同步状态status为0, 表示该线程第一次获得锁
        if (compareAndSetState(0, acquires)) {  //cas指令修改一下同步状态status
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {  //当前线程重入获取锁, state加1
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

addWaiter方法:入队列      

//AbstractQueuedSynchronizer的addWaiter方法, 添加等待者
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) { //判断有没有初始化, tail指向null, 队列没有初始化
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {  //先用cas指令加入一下队列
            pred.next = node;
            return node;
        }
    }
    enq(node);  //调用入队列方法
    return node;
}

//AbstractQueuedSynchronizer的enq方法, 加入队列方法
private Node enq(final Node node) {
    for (;;) {  //一直用cas指令去加入队列, 直到成功
        Node t = tail;
        if (t == null) { // Must initialize, 初始化队列, 把head设置一个Node, tail指向head
            if (compareAndSetHead(new Node()))  
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

acquireQueued方法:在队列中获取锁

//AbstractQueuedSynchronizer的acquireQueued方法, 在队列中获取锁
//入队列后, 如果前驱节点是head, 尝试获取锁, 获取失败就park
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)) { //当前节点的前驱节点是head, 尝试获取锁
                setHead(node);  //获得锁后, 把当前节点设置成head, 并把节点中的thread设为null
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //前驱节点不是head, 前驱节点的waitStatus设为Node.SIGNAL
            if (shouldParkAfterFailedAcquire(p, node) &&  
                parkAndCheckInterrupt())  //前驱节点设置成功, 当前线程park, 等待前驱节点释放锁并被unpark
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

总的来说使用了cas指令、队列、park(unpark)来实现了锁的功能。


还可以深入的知识

  1. AbstractQueuedSynchronizer 中的队列,使用了cas指令来控制并发,是一种自旋的方式;
  2. park和unpark这对方法,是不分前后的,一个线程可以先park再被unpark,也可以先unpark再park,这时会立即返回不会阻塞;

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值