ReentrantLock

 AQS的队列图

ReentrantLock是java.util.concurrent包提供的重入锁,其同步操作由AQS同步器提供支持。ReentrantLock提供了一些其他功能,包括定时的锁等待,可中断的锁等待,公平锁,非公平锁等。

ReentrantLock的独占并可重入:

新建一个ReentrantLock的时候可以通过传参true和flase来创建公平锁和非公平锁。

//默认就是非公平锁
public ReentrantLock() {
        sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
 }

fair参数如果不传,就默认创建一个非公平锁。

调用lock方法的时候,会到公平锁或者非公平锁的同步器进行不同的处理。

//实现非公平锁的同步器
   static final class NonfairSync extends Sync {
       final void lock() {
           ...
       }
   }
   
   //实现公平锁的同步器
   static final class FairSync extends Sync {
       final void lock() {
           ...
       }
   }

我们先看默认的非公平锁是怎么调用lock加锁的。

final void lock() {
       if (compareAndSetState(0, 1))
           //将当前线程赋值为独占锁
          setExclusiveOwnerThread(Thread.currentThread());
       else
            //否则表明锁已经被占用, 调用acquire让线程去同步队列排队获取
          acquire(1);
}

进入lock方法,会用CAS尝试进行加锁,也就是将同步的状态值state从0更新成1.如果更新成功就是加锁成功。将当前线程赋值为独占模式的持有者。

 “非公平”即体现在这里,如果占用锁的线程刚释放锁,state置为0,而排队等待锁的线程还未唤醒时,新来的线程就直接抢占了该锁,那么就“插队”了。

若当前有三个线程去竞争锁,假设线程A的CAS操作成功了,拿到了锁开开心心的返回了,那么线程B和C则设置state失败,走到了else里面,是下面的acquire方法。

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

acquire方法可以分成三步看。

第一步!tryAcquire(arg)是去尝试获取锁。

final boolean nonfairTryAcquire(int acquires) {
            //当前线程
            final Thread current = Thread.currentThread();
            //获取state变量值 0是没有线程获取锁 1是已经有线程获取到锁
            int c = getState();
            if (c == 0) { //如果没有线程获取到锁 再次尝试加锁
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //判断当前线程是否是持有锁线程 这里体现了可重入性
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                // 更新state值为新的重入次数
                setState(nextc);
                return true;
            }
            //获取锁失败
            return false;
        }

非公平锁tryAcquire的流程是:判断state是否等于0,如果等于0说明没有线程获取到锁,所以尝试加锁,如果state大于0,说明有线程持有锁,再判断当前线程是否是持有锁的线程。如果是持有锁的线程那么更新重入锁的次数,否则获取线程失败。

第二步acquireQueued(addWaiter(Node.EXCLUSIVE), arg))进入队列。假设ABC三个线程抢锁,A获取到锁后,那么B和C会进入队列进行等待。

//mode分为独占和共享模式 Node.EXCLUSIVE独占 Node.SHARED为共享
private Node addWaiter(Node mode) {
        //初始化节点,设置关联线程和模式(独占 or 共享)
        Node node = new Node(Thread.currentThread(), mode);
        // 获取队列的尾节点引用
        Node pred = tail;
        // 如果尾节点不为null 说明队列初始化过了,直接设置新节点为尾节点
        if (pred != null) {
            node.prev = pred;
            //通过cas进行设置尾节点,防止多线程丢失线程进列的操作
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // 尾节点为空,先初始化head节点并入队新节点
        enq(node);
        return node;
    }

private Node enq(final Node node) {
        //开始自转
        for (;;) {    
            //获取首节点
            Node t = tail;
            //首节点为空 cas设置首节点
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                //首节点不为空 说明初始化过了,那么就把当前节点设置为尾节点。
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

假如ABC三个线程抢锁,A获取到锁后,那么B和C会同时进入队列进行,进入addWaiter方法时,队列未初始化,假如BC同时进入enq进行首节点的cas的设置,第一轮BC通过cas抢夺首节点的设置,假如B成功了,那么C进行第二轮抢夺,这时首节点不为空了,那么C就抢夺尾节点的cas设置了。

//进入队列的节点再次尝试获取锁
final boolean acquireQueued(final Node node, int arg) {
        //是否成功获取锁
        boolean failed = true;
        try {
            //线程是否被中断过
            boolean interrupted = false;
            for (;;) {
                //获取前驱节点
                final Node p = node.predecessor();
                //如果前驱节点是head 并且尝试加锁成功
                if (p == head && tryAcquire(arg)) {
                    //加锁成功,将当前节点设置为head节点
                    setHead(node);
                    //清空原来头结点的引用(next)便于GC
                    p.next = null;
                    //获取成功
                    failed = false;
                    //返回中断标识 退出自旋
                    return interrupted;
                }
                //获取失败后是否挂起线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                //最终都没能获取同步状态,结束该线程的请求
                cancelAcquire(node);
        }
    }
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //前驱节点的状态
        int ws = pred.waitStatus;
        //判断是否要阻塞当前线程
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
            //状态>0 遍历前驱结点直到找到不是结束状态的结点
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            // 将前驱节点的状态设置为SIGNAL状态
            //确保当前结点的前驱结点的状态为SIGNAL,SIGNAL意味着线程释放锁后会唤醒后面阻塞的线程。                    毕竟,只有确保能够被唤醒,当前线程才能放心的阻塞。
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
//只有当前驱节点是唤醒状态,才挂起当前结点
private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

注意:只有在前驱结点已经是SIGNAL状态后才会执行后面的方法立即阻塞,对应上面的第一种情况。其他两种情况则因为返回false而重新执行一遍。       

 释放锁:

public void unlock() {
        sync.release(1);
    }
public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;//获取首节点
            
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
//释放锁
protected final boolean tryRelease(int releases) {

            //获取state状态-1的数值
            int c = getState() - releases;
            //判断当前线程是否是持有锁的线程
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //如果是0 说明可以释放锁
            if (c == 0) {
                free = true;
                //将持有锁的线程赋值null
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

private void unparkSuccessor(Node node) {
        //获取节点的状态
        int ws = node.waitStatus;
        //如果小于0 就设置为0 就是初始化无状态
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        //找到当前节点的下一个节点
        Node s = node.next;
        //如果下个节点是null或者状态>0 (节点因超时或被中断而取消时设置状态为取消状态)
        if (s == null || s.waitStatus > 0) {
            s = null;
            //循环遍历找到需要唤醒的线程
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        //唤醒下一个线程
        if (s != null)
            LockSupport.unpark(s.thread);
    }

公平锁:

公平锁是进来直接调用acquire(1)方法,非公平锁是进来直接抢锁。

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

下面是非公平的加锁解锁过程图

总结:

ReentrantLock本身也是一种支持重进入的锁:即该锁可以支持一个线程对共享资源重复加锁;

ReentrantLock公平锁:只尝试抢锁一次(进入acquire(1)判断state==0的时候抢锁),入列后等待解锁后的线程进行唤醒。

ReentrantLock非公平锁:抢锁两次,进来后先尝试抢锁,如果没有抢到在进入acquire(1)判断state==0的时候再次抢锁,进入队列后也只能等待解锁后的线程进行唤醒。

使用场景:

如果发现该操作已经在执行中则不再执行,多用于定时任务,一个资源只有一个操作。

if (lock.tryLock()) {  //如果已经被lock,则立即返回false不会等待,达到忽略操作的效果 

                    try {

                       //操作

                    } finally {
                        lock.unlock();
                    }

                }

如果发现该操作已经在执行,等待一个一个执行。比如同时写一个文件,不同情景同时修改一条信息。

try {
                        lock.lock(); //如果被其它资源锁定,会在此等待锁释放,达到暂停的效果

                       //操作

                    } finally {
                        lock.unlock();
                    }

如果发现该操作已经在执行,则尝试等待一段时间,等待超时则不执行。使用超时机制。

try {
                    if (lock.tryLock(5, TimeUnit.SECONDS)) {  //如果已经被lock,尝试等待5s,看是否可以获得锁,如果5s后仍然无法获得锁则返回false继续执行


                        try {


                            //操作


                        } finally {
                            lock.unlock();
                        }


                    }
                } catch (InterruptedException e) {
                    e.printStackTrace(); //当前线程被中断时(interrupt),会抛InterruptedException                 
                }

如果发现该操作已经在执行,等待执行。这时可中断正在进行的操作立刻释放锁继续下一操作。

取消正在同步运行的操作,来防止不正常操作长时间占用造成的阻塞。

try {
                    lock.lockInterruptibly();
                    //操作

                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值