各种同步器的实现原理

ReentrantLock 的上锁解锁以及条件队列

ReentrantLock 上锁过程

所有的同步器都是以 AQS 为基础写的。从设计模式的角度来看,开发者运用了模板方法设计模型来了实现同步器这套东西的。

其中,有两部分非常重要,一个是修改 state 的逻辑,也就是 AQS 的实现类是如何实现 tryAcquire() 方法;另外一个入队列和出队列的逻辑。其中后面一个是基本相同的,前一个根据不同同步器实现的。

先来看看 ReentrantLock 的大体上流程:
ReentrantLock#lock()方法程序流程图

  1. ReentrantLock#lock() 方法直接调用了 NonFairSync#lock() 方法。NonFairSync 继承自 Sync 抽象类。而 Sync 抽象类又继承了 AQS 抽象了。这整的和俄罗斯套娃了。其实,我对模板方法这种设计模式不太熟悉,模板方法的要义就将逻辑相同的代码写到抽象类中,这样就避免了重复开发。其中我们看到,addWaiter() 和 acquireQueued() 方法都在 AQS 中,这说明入队和出队的操作都是由 AQS 完成的。
  2. 还可以看到 aquire() 和 tryAquire() 方法都在 NonFairSync 中,这说明修改 state 逻辑都是由具体的同步器自己完成的。ReentrantLock 如果没有 state 是 1 ,则进入等待状态,如果 state = 0 ,则使用 CAS 修改 state 的值。
  3. 从图中可以看到,如果线程的运气不好,一直都没有申请到锁, 会经历三次申请锁。第一次是在 aquire 方法中,CAS 修改 state 的值。第二次是 tryAquire 中,第三次是在 aquireQueue 中,这有点特殊的是本线程所代表的节点的上一个节点必须是头节点才能抢锁,否则的话,则不能抢锁。
  4. 上面的图是以非公平锁的实现画的。

下面是公平锁和非公平锁的实现。其实我们只需要看看 aquire 和 tryAcquire 两个方法就行了,其他的方法都在 AQS 中。

// 有两个,一个是公平锁,一个非公平锁
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) {
            //这是公平锁有特色的地方,先查看同步队列(CLH)中是否正在等待的队列。如果没有再申请锁,如果有
            // 则进入入队的操作。
            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;
    }
// 非公平锁
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * 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);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                // 非公平锁就不太一样了。不管队列中是否有锁,上来就抢锁,抢到就接着执行临界区,抢不到就入队。
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }

两个 tryAcquire() 方法里面,实际上都是修改 state 的值,都是从 0 修改成 acquire 的值。实际上 acquire 的值只可能是 1 或者 0 。

ReentrantLock 的解锁过程

ReentrantLock 的解锁过程,先是调用 AQS 的 release 方法,release 方法中又调用了 Sync 的 tryAquire 方法。在 tryAquire 方法中是 ReentrantLock 释放锁的逻辑,主要的功能是将 state 方法修改为 0 ,唤醒 CLH 队列(同步队列)中下一个排队的队列,然后继续 requireQueue 的循环,去争抢锁,如果争抢到来则跑逻辑代码,如果争抢不到就继续 park。

ReentrantLock解锁过程

解锁过程注意两个问题:

  1. 获得锁的线程,不在使用 CAS 实现 ,因为已经获得了锁,不需要再做临界区同步了。
  2. 获得锁的线程需要,再同步队列中需要唤醒下一个线程。所以需要向后的引用,然后在 requiredQueue 中需要向前找 head 节点,所以需要向前的引用,基于这两点的考虑,CLH 队列是使用的是双向链表。

条件队列

条件队列是 synchronized 中 wait/notify 的替代方案,比 wait/notify 优秀的一点是一个 ReentrantLock 可以 new 出多个条件队列,而 synchronized 只能有一个条件队列。这也能进行更精确的控制。例如,生产者和消费,为生产者和消费者个准备两个队列,当生产者生产出消息后,signal 消费者条件队列,这样就能只唤醒消费者,而不用唤醒生产者。如果要是使用 synchronized 的方式,生产者和消费者都在一个条件队列中,唤醒的可能是消费者线程,也可能是生产者线程。

条件队列的类是 ConditionObject,它是 AQS 的内部类,而且是普通的内部类。所以它是放到 newCondition() 这个普通方法中的。它还实现了 Condition 接口。此接口重要的两个方法是:

  1. await() 让出锁,自己进入 park() 等待。
  2. signal():通知唤醒条件队列中的线程,他们可以干活了。

先来 await 和 signal 方法的执行流程:

ConditionObject await/signal 的程序流程图

  1. ReentrantLock 的源代码之所以难度,我觉的是多线程造成的。尤其是涉及到线程通信的部分,要多个线程一起共同工作才能完成,state、条件队列、同步队列的维护。
  2. 有两个线程 A 和 B 。 当 A 执行 await 之后,A 进入条件队列, wait 状态,当 B 执行 signal 的时候才会把 A 从条件队列移动到同步队列。当 B 执行 unlock() 的时候,A 才有可能被唤醒。所以看 JUC 的源代码一定要有多线程的场景。当线程执行到 LockSupport.park() ,就意识到只有到其他线程 LockSupport.unpark() 的时候才能从 wait 状态到 runnable 状态。
  3. addConditionWaiter 方法是向条件队列插入节点的方法,由于当前线程已经持有了锁,所以没有线程同步的代码。
  4. fullyRelease() 方法是释放当面的锁,并唤醒在同步队列中的线程。具体的做法是将 state 减到 0 ,然后将 head 之后的线程 LockSupport.unpark() 起来。在这里可以直到,在条件队列中的线程是不可能在条件队列就被唤醒,只有移动到同步队列才会被唤醒。
  5. inSyncQueue,当前线程是否在同步队列中,如果不在进入 wait,如果在则进入 aquiredQueue 过程。
  6. acquireQueue 方法先看看自己的上一个节点是否是头节点,如果是头节点,则执行业务代码,如果不是,则继续进入 wait 状态。
  7. transforSignal() 方法是将当条件队列中的头节点移动到同步队列中,并修改里面的状态。

举个 1 A 2 B 3 C 的多线程经典面试题的例子,这样就更加的印象深刻了。

面试题的题目是这样的:

有三个线程,分别让他们依次输出 A、B、C。

可以使用 sychronized 方式实现,也可以使用 Condition 来实现。下面是 Condition 实现的。


import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @className: A1B2C3ReentrantLockCondition
 * @Description:
 * @Author: wangyifei
 * @Date: 2022/9/4 17:43
 */
public class A1B2C3ReentrantLockCondition {
    public static int i = 1 ;
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Condition condA = lock.newCondition();
        Condition condB = lock.newCondition();
        Condition condC = lock.newCondition();

        Runnable r1 = () ->{
            while (true) {
                lock.lock();
                try {
                    while (i != 1) {
                        try {
                            condA.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("a");
                    i = 2;
                    condB.signal();
                } finally {
                    lock.unlock();
                }
//                System.out.println("r1 end");
            }
        };

        Runnable r2 = () -> {
            while(true) {
                lock.lock();
                try {
                    while (i != 2) {
                        try {
                            condB.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("b");
                    i = 3;
                    condC.signal();
                } finally {
                    lock.unlock();
                }
//                System.out.println("r2 end");
            }
        };

        Runnable r3 = () -> {
            while(true){
//                System.out.println("r3 in ");
                lock.lock();
                try {
//                    System.out.println("r3 get lock");
                    while(i != 3){
                        try {
                            condC.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("c");
                    i = 1 ;
                    condA.signal();
                }finally {
                    lock.unlock();
                }
            }
        };

        ThreadPoolExecutor pool = new ThreadPoolExecutor( 3 , 3 , 1000 , TimeUnit.SECONDS , new ArrayBlockingQueue<Runnable>(100));
        pool.execute(r1);
        pool.execute(r2);
        pool.execute(r3);
        pool.shutdown();
    }
}

1

纵坐标代表线程,横坐标代表时间点。对于横坐标中数字的解释为:

  1. 通过 lock() 函数,C 获得了锁,A 和 B 进入同步队列中。
  2. C 线程判断 i 的值不等于 3 ,则执行 await ,C 线程进入 wait 状态。并且在 await 方法中,将 A 和 B 两个线程唤醒其中之一,加入唤醒的是 B, 则 B 开始执行判断 i 的。
  3. B 判断 i 的值不等于 2 ,B 调用 await 方法,唤醒同步队列中的 A。B 自己进入到条件队列中。
  4. A 执行逻辑代码,i 现在等于 1 ,则打印字符串 A,并执行 signal() 方法,signal 方法条件队列中的所有节点到同步队列中,然后 A 执行 unlock 方法,将唤醒同步队列中的节点
  5. C 看到 i 还是不等于 3 ,则又开始执行 await ,又进入了条件队列,然后将 B 唤醒。
  6. B 醒了之后,发现 i 等于 2 ,则打印 B ,并执行 signal 方法,signal 方法会将从条件队列中移动到条件队列中,然后使用 unlock 方法将 C 唤醒。
  7. C 醒来之后,判断 i 等于 3 ,打印 C ,执行 signal 和 unlock 方法,结束。

ReentrantReadWriteLock 读写锁

下面来看看,ReentrantReadWriteLock ,这个比较特殊的,分成了一个读锁和写锁,他们分别他们分别占了 state 的前 16 位和后 16 位。其中,写锁是不可以重入的,下面的代码中也没有判断重入的情况,只是记录了写线程的数量。要注意一下几点:

  1. 读锁不可重入,写锁是可重入的。
  2. 读锁不支持条件变量。
  3. 在锁的重入逻辑中,读锁不能升级为写锁,也就先获取了读锁的线程不能做重入获取写锁。
  4. 在锁的重入逻辑中,写锁可以降级为锁,也就线程再获取了写锁后,仍然可以重入获取写锁。

protected final int tryAcquireShared(int unused) {
            /*
             * Walkthrough:
             * 1. If write lock held by another thread, fail.
             * 2. Otherwise, this thread is eligible for
             *    lock wrt state, so ask if it should block
             *    because of queue policy. If not, try
             *    to grant by CASing state and updating count.
             *    Note that step does not check for reentrant
             *    acquires, which is postponed to full version
             *    to avoid having to check hold count in
             *    the more typical non-reentrant case.
             * 3. If step 2 fails either because thread
             *    apparently not eligible or CAS fails or count
             *    saturated, chain to version with full retry loop.
             */
            Thread current = Thread.currentThread();
            int c = getState();
            if (exclusiveCount(c) != 0 &&
                //这里的代码表明,当线程获得了写锁,还是可以获得读锁的。
                getExclusiveOwnerThread() != current)
                return -1;
            int r = sharedCount(c);
            // 这里的代码和排他锁(写锁)的方式不一样的,这里只对读线程的数量做了累加。
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                // state 的读段 + 1 
                compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    // 如果是重入的锁,则进行 + 1 处理。这里就和 ReentrantLock 的不一样,只要
                    // 大家都是读的操作,就互相不影响,所以不用重入操作。
                      firstReaderHoldCount++;
                } else {
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            return fullTryAcquireShared(current);

        final boolean tryWriteLock() {
            Thread current = Thread.currentThread();
            int c = getState();
            if (c != 0) {
                int w = exclusiveCount(c);
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
            }
            if (!compareAndSetState(c, c + 1))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

        protected final boolean tryAcquire(int acquires) {
            /*
             * Walkthrough:
             * 1. If read count nonzero or write count nonzero
             *    and owner is a different thread, fail.
             * 2. If count would saturate, fail. (This can only
             *    happen if count is already nonzero.)
             * 3. Otherwise, this thread is eligible for lock if
             *    it is either a reentrant acquire or
             *    queue policy allows it. If so, update state
             *    and set owner.
             */
            Thread current = Thread.currentThread();
            int c = getState();
            int w = exclusiveCount(c);
            if (c != 0) {
                // (Note: if c != 0 and w == 0 then shared count != 0)
                // current != getExclusiveOwnerThread() 保证了获得了读锁的线程不能获得写锁。因为当有读锁的时候,写锁的后16位是 0 ,getExclusiveOwnerThread() 返回的是 null。
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire ,这里是可重入的设置,当获得写锁的线程再次进入 lock 的逻辑,将 state + 1 ,返回 true ,不需要进入队列。
                setState(c + acquires);
                return true;
            }
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

CountdownLatch

CountdownLatch 倒计时同步器的逻辑。有三个关键的地方:

  1. 构造函数。
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
        Sync(int count) {
            setState(count);
        }

再来看看 countDown() ,

    public void countDown() {
        sync.releaseShared(1);
    }
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0; //
            }
        }

从 tryReleaseShared 方法中,我们可以知道,释放锁的条件是 nextc == 0 ,也就是 state 的值是否等于 0 ,如果等于 0 才执行 doReleaseShared() 函数。每当执行一次 countDown() 函数的时候,state 的值就减去 1 ,当 state 的值为 0 的时候,说明所以的线程已经执行结束了,说明所有的线程已经准备就绪,可以通知执行下一个操作了。

最后看看 await 函数:

        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

从 await() 函数来看,它的判断条件也是 state == 0 ,如果等于 0 才会往下执行,否则进入同步队列,进入 wait 状态。

总结来说,在构造函数里面,先设置 state 为正整数,在 countDown() 函数中减去1,如果 state = 0 则当前线程立刻唤醒同步队列中的线程,也就是后面正在等待的线程。如果 state = 0 ,才能执行逻辑代码,否则的话,进入等待队列。

CountdountLatch 的两个例子:

  1. 批处理,t1 t2 处理,主线程在等待。t1、t2 执行完以后执行 countDown() ,主线程则执行 await() 函数,等待,t1 和 t2 两个线程执行结束后,再执行主线程的逻辑。此功能可以替代 join() 函数的功能。
  2. 线程池的资源限制。这个不对,我们设置 count = 5 ,当有第 6 个线程启动的时候,则 state = 0 ,则直接就 执行了。不会停主,而是往下执行。倒是 Semaphore 合适在这种场景使用。

Semaphore

Semaphore 有几个关键的地方:

构造函数,也是可以初始化了 state,

public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}

acquire 这个方法是往 state 减 1 的。

       public final void acquireSharedInterruptibly(int arg)
               throws InterruptedException {
           if (Thread.interrupted())
               throw new InterruptedException();
           if (tryAcquireShared(arg) < 0)
               doAcquireSharedInterruptibly(arg);
       }

   final int nonfairTryAcquireShared(int acquires) {
               for (;;) {
                   int available = getState();
                   int remaining = available - acquires;
                   if (remaining < 0 ||
                       compareAndSetState(available, remaining))
                       return remaining;
               }
           }

tryAcquireShared 里面调用的是nonfairTryAcquireShared,我们看到它返回的是 remaining ,而 acquireSharedInterruptibly 是当 tryAcquireShared < 0 的时候,也就是在 state 小于 0 的时候,调用 acquire() 的线程会进入同步队列。所以 Semaphore 适用于有限资源的分配,也就是许可,获得许可的线程才能使用资源。

再来看看如何还许可,下面的 tryRelaseShared(int) 是修改 state 的值。

protected final boolean tryReleaseShared(int releases) {
    for (;;) {
        int current = getState();
        int next = current + releases;
        if (next < current) // overflow
            throw new Error("Maximum permit count exceeded");
        if (compareAndSetState(current, next))
            return true;
    }
}

从此函数中可以得知,此功能是往 state 往加 1 的,可以看到它不会做 state <= permit 的判断。这是因为只要
获得许可的线程才能还许可,所以不会让 state 最后大于 permits 。

CycliBarrier

CyclicBarrier 和 CountdownLatch 的功能差不多,但是它是可以重复使用的。需要注意的是下面的几点。

构造函数。在构造函数里面会初始化两个参数,一个是 parties,这其实是 state 的初始值,barrierAction ,这个是当其他线程都执行结束以后,需要执行的代码。

       public CyclicBarrier(int parties, Runnable barrierAction) {
           if (parties <= 0) throw new IllegalArgumentException();
           this.parties = parties;
           this.count = parties;
           this.barrierCommand = barrierAction;
       }

await() ,其实里面就是执行的 doawait() 这个函数。CyclicBarrier 和之前的同步器有所不同,只有这么一个方法。

       private int dowait(boolean timed, long nanos)
           throws InterruptedException, BrokenBarrierException,
                  TimeoutException {
           // 使用可重入锁来保证
           final ReentrantLock lock = this.lock;
           lock.lock();
           try {
               final Generation g = generation;
   
               if (g.broken)
                   throw new BrokenBarrierException();
   
               if (Thread.interrupted()) {
                   breakBarrier();
                   throw new InterruptedException();
               }
   
               int index = --count;
               // 当 state 为 0 的情况下,执行 command 的 run 函数。
               if (index == 0) {  // tripped
                   boolean ranAction = false;
                   try {
                       final Runnable command = barrierCommand;
                       if (command != null)
                           command.run();
                       ranAction = true;
                       nextGeneration();
                       return 0;
                   } finally {
                       if (!ranAction)
                           breakBarrier();
                   }
               }
   
               // loop until tripped, broken, interrupted, or timed out
               for (;;) {
                   try {
                       if (!timed)
                           trip.await();
                       else if (nanos > 0L)
                           nanos = trip.awaitNanos(nanos);
                   } catch (InterruptedException ie) {
                       if (g == generation && ! g.broken) {
                           breakBarrier();
                           throw ie;
                       } else {
                           // We're about to finish waiting even if we had not
                           // been interrupted, so this interrupt is deemed to
                           // "belong" to subsequent execution.
                           Thread.currentThread().interrupt();
                       }
                   }
   
                   if (g.broken)
                       throw new BrokenBarrierException();
   
                   if (g != generation)
                       return index;
   
                   if (timed && nanos <= 0L) {
                       breakBarrier();
                       throw new TimeoutException();
                   }
               }
           } finally {
               lock.unlock();
           }
       }

// 重置 generation 

    private void nextGeneration() {
        // signal completion of last generation
        trip.signalAll();
        // set up next generation
        count = parties;
        generation = new Generation();
    }

  1. await 中调用的是 doAwait(false , 0), doAwait lock.lock() try 的结构,获得锁的线程都会执行 count-- ,也就是当完成任务的线程数到达 parties 之后,就会调用 barrierCommand,然后执行 newGeneration() 函数,count 复位, generation 新建一个对象,然后所有 signalAll 所有在条件队列上等待的线程,把它们唤醒,其实就是把它们从条件队列移动到同步队列。
  2. Semaphore 其实使用的是 Condition 的功能来实现的。

StampedLock

StampedLock 时间戳同步器。这个同步器是对读写锁优化。下面是一个例子:

  private double x, y;//内部定义表示坐标点
    private final StampedLock s1 = new StampedLock();//定义了StampedLock锁,

    void move(double deltaX, double deltaY) {
        long stamp = s1.writeLock();//这里的含义和distanceFormOrigin方法中 s1.readLock()是类似的
        try {
            x += deltaX;
            y += deltaY;
        } finally {
            s1.unlockWrite(stamp);//退出临界区,释放写锁
        }
    }

    double distanceFormOrigin() {//只读方法
        long stamp = s1.tryOptimisticRead();  //试图尝试一次乐观读 返回一个类似于时间戳的邮戳整数stamp 这个stamp就可以作为这一个所获取的凭证
        double currentX = x, currentY = y;//读取x和y的值,这时候我们并不确定x和y是否是一致的
        if (!s1.validate(stamp)) {//判断这个stamp是否在读过程发生期间被修改过,如果stamp没有被修改过,责任无这次读取时有效的,因此就可以直接return了,反之,如果stamp是不可用的,则意味着在读取的过程中,可能被其他线程改写了数据,因此,有可能出现脏读,如果如果出现这种情况,我们可以像CAS操作那样在一个死循环中一直使用乐观锁,知道成功为止
            stamp = s1.readLock();//也可以升级锁的级别,这里我们升级乐观锁的级别,将乐观锁变为悲观锁, 如果当前对象正在被修改,则读锁的申请可能导致线程挂起.
            try {
                currentX = x;
                currentY = y;
            } finally {
                s1.unlockRead(stamp);//退出临界区,释放读锁
            }
        }
        return Math.sqrt(currentX * currentX + currentY * currentY);
    }
}

从上面的例子中,我们可以得到下面的结论。

long stamp = s1.writeLock();
try {
   // do something
   // 1. 当有线程获得了写锁,其他的锁是不能再获得锁了。
} finally {
    s1.unlockWrite(stamp);
}
long stamp = s1.tryOptimisticRead(); 
// do something 
// 2. 当进入乐观的读锁之后,需要写入数据的线程还是可以得到锁,进行写操作。
// 3. s1.validate(stamp) 会根据时间戳来检查一下是否有写线程来写入过东西。
//    如果有写线程来了,并对数据进行了更改,则 validate 返回的是 false 
// 可以使用 while 的方式来
// while(!s1.validate(stamp)){
//   stamp = s1.tryOptimisticRead(); 
//   do something
//}
// do the next thing
// 也可以将 tryOptimisticRead 乐观锁 升级到读锁(悲观锁)
if(!s1.validate(stamp)){
	stamp = s1.readLock();
    try{
    	// do something
    }finally{
    	s1.unlockRead(stamp)
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值