• ReentrantLock学习笔记


    参考:https://www.jianshu.com/p/4358b1466ec9

    前言:

    先来想象一个场景:手把手的进行锁获取和释放,先获得锁A,然后再获取锁B,当获取锁B后释放锁A同时获取锁C,当锁C获取后,再释放锁B同时获取锁D,以此类推,这种场景下,synchronized关键字就不那么容易实现了,而使用Lock却显得容易许多。

    源码:

    public class ReentrantLock implements Lock, java.io.Serializable {
        private final Sync sync;
        abstract static class Sync extends AbstractQueuedSynchronizer {
    
            /**
             * Performs {@link Lock#lock}. The main reason for subclassing
             * is to allow fast path for nonfair version.
             */
            abstract void lock();
    
            /**
             * Performs non-fair tryLock.  tryAcquire is implemented in
             * subclasses, but both need nonfair try for trylock method.
             */
            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;
                    if (nextc < 0) // overflow
                        throw new Error("Maximum lock count exceeded");
                    setState(nextc);
                    return true;
                }
                return false;
            }
    
            protected final boolean tryRelease(int releases) {
                int c = getState() - releases;
                if (Thread.currentThread() != getExclusiveOwnerThread())
                    throw new IllegalMonitorStateException();
                boolean free = false;
                if (c == 0) {
                    free = true;
                    setExclusiveOwnerThread(null);
                }
                setState(c);
                return free;
            }
    
        }
        //默认非公平锁
        public ReentrantLock() {
            sync = new NonfairSync();
        }
        //fair为false时,采用公平锁策略
        public ReentrantLock(boolean fair) {    
            sync = fair ? new FairSync() : new NonfairSync();
        }
        public void lock() {
            sync.lock();
        }
        public void unlock() {    sync.release(1);}
        public Condition newCondition() {    
            return sync.newCondition();
        }
        ...
    }

    使用方式:

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    lock.lock();
    try {
      while(条件判断表达式) {
          condition.wait();
      }
     // 处理逻辑
    } finally {
        lock.unlock();//保证锁在最后能够释放 注意!!!!一定要释放
    }

     非公平锁:

    当线程获取锁失败的时候,同样可以自旋获取锁,超过自旋次数才会进入队列尾部

    • 线程A和B同时执行CAS指令,假设线程A成功,线程B失败,则表明线程A成功获取锁,并把同步器中的exclusiveOwnerThread设置为线程A。
    • 竞争失败的线程B,在nonfairTryAcquire方法中,会再次尝试获取锁,Doug lea会在多处尝试重新获取锁,应该是在这段时间如果线程A释放锁,线程B就可以直接获取锁而不用挂起
    static final class NonfairSync extends Sync {
        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
    
        public final void acquire(int arg) {    
            if (!tryAcquire(arg) && 
                    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))       
              selfInterrupt();
        }
    
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

    公平锁:

    在公平锁中,每当线程执行lock方法时,如果同步器的队列中有线程在等待,则直接加入到队列尾部。

    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
    
        final void lock() {
            acquire(1);
        }
    
        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        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;
        }
    }

    重入锁:

    可重入锁指的是在一个线程中可以多次获取同一把锁,比如:
    一个线程在执行一个带锁的方法,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行调用的方法,而无需重新获得锁;

    if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }

    条件变量:

    public class ConditionObject implements Condition, java.io.Serializable {
        /** First node of condition queue. */
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        private transient Node lastWaiter;
        public final void signal() {}
        public final void signalAll() {}
        public final void awaitUninterruptibly() {}  
        public final void await() throws InterruptedException {}
    }
    • Synchronized中,所有的线程都在同一个object的条件队列上等待。而ReentrantLock中,每个condition都维护了一个条件队列。
    • 每一个Lock可以有任意数据的Condition对象,Condition是与Lock绑定的,所以就有Lock的公平性特性:如果是公平锁,线程为按照FIFO的顺序从Condition.await中释放,如果是非公平锁,那么后续的锁竞争就不保证FIFO顺序了。
    • Condition接口定义的方法,await对应于Object.waitsignal对应于Object.notifysignalAll对应于Object.notifyAll。特别说明的是Condition的接口改变名称就是为了避免与Object中的wait/notify/notifyAll的语义和使用上混淆。

     await实现逻辑:

    1. 将线程A加入到条件等待队列中,如果最后一个节点是取消状态,则从对列中删除。
    2. 线程A释放锁,实质上是线程A修改AQS的状态state为0,并唤醒AQS等待队列中的线程B,线程B被唤醒后,尝试获取锁,接下去的过程就不重复说明了。
    3. 线程A释放锁并唤醒线程B之后,如果线程A不在AQS的同步队列中,线程A将通过LockSupport.park进行挂起操作。
    4. 随后,线程A等待被唤醒,当线程A被唤醒时,会通过acquireQueued方法竞争锁,如果失败,继续挂起。如果成功,线程A从await位置恢复。

    signal实现逻辑:

    1. 接着上述场景,线程B执行了signal方法,取出条件队列中的第一个非CANCELLED节点线程,即线程A。另外,signalAll就是唤醒条件队列中所有非CANCELLED节点线程。遇到CANCELLED线程就需要将其从队列中删除。
    2. 通过CAS修改线程A的waitStatus为0,表示该节点已经不是等待条件状态,并将线程A插入到AQS的等待队列中。
    3. 唤醒线程A,线程A和别的线程进行锁的竞争。
  • 相关阅读:
    linux下小知识点积累
    马斯洛需求层次理论
    tar命令的小经验
    shell 和c语言的区别
    使用vue实现的品牌列表简单小例子
    vue的基本代码以及常见指令
    MVC和MVVM
    CSS3幽灵
    Web版App,原生App,混合App的区别以及优缺点
    常见的sql操作
  • 原文地址:https://www.cnblogs.com/L-a-u-r-a/p/8563815.html
Copyright © 2020-2023  润新知