深入理解ReentrantReadWriteLock

深入理解ReentrantReadWriteLock

一、为什么要出现读写锁

synchronized和ReentrantLock都是互斥锁

如果说有一个操作是读多写少的,还要保证线程安全的话,如果采用上述的两种互斥锁,效率方面肯定是很低的

在这种情况下,咱们就可以用ReentrantReadWirteLock读写锁去实现

读读之间是不互斥的,可以读和读操作并发执行操作

但是如果涉及到写操作,那么还得是互斥得操作

public class IReentrantReadWriteLock {
    private final static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private final static ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
    private final static ReentrantReadWriteLock.ReadLock readLock = lock.readLock();

    public static void main (String[] args) throws InterruptedException {
        System.out.println("=====USE ReentrantReadWriteLock-> readLock=====");
        new Thread(() -> {
            readLock.lock();
            System.out.println(Thread.currentThread().getName() + " 获取到读锁资源,并睡眠:" + (60 * 60 * 1000) + " ");
            try {
                Thread.sleep(60 * 60 * 1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "lock-1").start();
        Thread.sleep(1000);
        readLock.lock();
        System.out.println(Thread.currentThread().getName() + " 获取到读锁资源");
        readLock.unlock();
        System.out.println("=====USE ReentrantReadWriteLock-> readLock=====");

        System.out.println("=====USE ReentrantReadWriteLock-> writeLock=====");
        new Thread(() -> {
            writeLock.lock();
            System.out.println(Thread.currentThread().getName() + " 获取到写锁资源,并睡眠:" + (60 * 60 * 1000) + " ");
            try {
                Thread.sleep(60 * 60 * 1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "lock-2").start();
        Thread.sleep(1000);
        // 主线程是拿不到写锁,写锁互斥
        writeLock.lock();
        System.out.println(Thread.currentThread().getName() + " 获取到读锁资源");
        writeLock.unlock();
        System.out.println("=====USE ReentrantReadWriteLock-> writeLock=====");
    }
}

二、读写锁得实现原理

ReentrantReadWriteLock还是基于AQS实现的,还是对state进行操作,拿到锁资源就去干活,如果没有拿到,依然去AQS队列中排队

读锁操作:基于state的高16位进行操作

写锁操作:基于state的低16位进行操作

ReentrantReadWirteLock依然是可重入锁

写锁重入: 读写锁中的写锁的重入方式,基本和ReentrantLock一致,没有什么区别,依然是对state进行+操作即可,只要确认持有锁资源的线程,是当前写锁线程即可,只不过之前ReentrantLock的重入次数是state的正数取值范围,但是读写锁中的写锁范围就变小了,基于state的低16位进行操作

读锁重入: 因为读锁是共享锁,读锁在获取资源操作时,只要对state的高16进行+1操作,因为读锁是共享锁,所以同一时间会有多个线程持有读锁资源,这样一来,多个读操作在持有读锁时,无法确认每个线程读锁重入的次数,为了去记录读锁重入的次数,每个读锁的线程,都会有个ThreadLock记录锁重入的次数

写锁的饥饿问题: 读锁是共享的,当有线程持有读锁资源时,再来一个线程想要获取读锁,直接对state修改即可,在读锁资源先被占用后,来了一个写锁资源,此时,大量的需要获取读锁的线程来请求锁资源,如果可以绕过写锁,直接拿锁资源,会造成写锁长时间无法获取到写锁资源

读锁在拿到锁资源后,如果再有读线程需要获取读锁资源,需要去AQS队列排队,如果队列的前面有写锁资源的线程,那么后续读锁线程是无法拿到锁资源的,持有读锁的线程,只会让写锁线程之前的读线程拿到锁资源

三、写锁分析

3.1 写锁的加锁流程

image.png

写锁的加锁流程:

  1. 写线程来竞争写锁资源
  2. 写线程会直接通过tryAcquire获取写锁资源(公平锁&非公平锁)
  3. 获取state值,并且拿到低16位的值
  4. 如果state值不为0,判断是否是锁重入操作,判断当前持有锁线程是否是当前线程
  5. 如果state值为0
    1. 判断是公平锁:查看队列是否有排队,有就去排队,没有抢一下
    2. 判断是非公平锁:直接抢锁资源
  6. 如果拿到锁资源,直接告辞,如果没有拿到去排队,而排队的逻辑和ReentrantLock一样

3.1 写锁的加锁源码分析

  • lock()
// 写锁加锁入口
public void lock() {
    sync.acquire(1);
}
  • acquire(int arg)
 public final void acquire(int arg) {
     // tryAcquire(arg) 尝试获取锁
    if (!tryAcquire(arg) &&
	 // acquireQueued 和 ReetrantLock一样
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  • boolean tryAcquire(int acquires)
protected final boolean tryAcquire(int acquires) {
	// 获取到当前线程
    Thread current = Thread.currentThread();
	// 拿到state的值
    int c = getState();
	// 得到state低16位的值,为了判断是否有读锁,读写互斥
    int w = exclusiveCount(c);
	// c不等于说明有线程持有锁
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
		// w == 0 说明读锁占用了,读写互斥,写锁去排队
		// w!=0 判断当前线程是否是持有锁线程,如果不是,就去排队
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
		// 判断锁重入次数,最大重入次数65535
		// static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
		// 锁重入
        setState(c + acquires);
		// 锁重入成功
        return true;
    }
	// 尝试获取锁
    if (writerShouldBlock() ||
		// CAS拿锁,拿锁失败返回false去排队
        !compareAndSetState(c, c + acquires))
        return false;
	// 拿锁成功,设置占有互斥锁的线程
    setExclusiveOwnerThread(current);
    return true;
}
  • exclusiveCount(int c)
// 这个方法就是将state的低16位拿到
// static final int SHARED_SHIFT   = 16;
// static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
// static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
// 00000000 00000000 00000000 00000001    ==  1
// 00000000 00000001 00000000 00000000    ==  1 << 16 (1往左移16)
// 00000000 00000000 11111111 11111111    ==  EXCLUSIVE_MASK (1 << 16) - 1 (1往左移16) -1
// &运算,一个为0,必然是0,都为1才会使1
// 通过计算如果state值是高16位,state & EXCLUSIVE_MASK,肯定是0,说明读锁占用了
// 读锁每次对高16位进行加 c + SHARED_UNIT
  • writerShouldBlock() 公平锁
// 公平锁和ReentrantLock一样
final boolean writerShouldBlock() {
     // 查看是否有线程在AQS中排队
    return hasQueuedPredecessors();
}
  • hasQueuedPredecessors()
public final boolean hasQueuedPredecessors() {
    // 尾节点
    Node t = tail;
    // 头节点
    Node h = head;
    Node s;
   // 条件1 h != t
   // 当h == t,则 h!=t 返回false
   // 条件2 ((s = h.next) == null || s.thread != Thread.currentThread())
   //  s 为头节点下一个节点 
   //  s 节点不为null,并且是节点的线程为当前线程(排在第一名的是不是我)
   // 当 头节点下一个节点的线程 == 当前线程,则 s.thread != Thread.currentThread() 返回false
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}
  • writerShouldBlock() 非公平锁
// 非公平锁
final boolean writerShouldBlock() {
    return false; // writers can always barge
}

3.3 写锁释放流程&源码分析

释放的流程和ReentrantLock一致,只是判断释放释放干净时,判断低16位的值

  • unlock()
public void unlock() {
    // 释放锁资源不分为公平锁和非公平锁,都是一个sync对象
    sync.release(1);
}
  • release(int arg)
// 释放锁的核心流程
public final boolean release(int arg) {
    // 核心释放锁资源,对state-1操作
    if (tryRelease(arg)) {
	// 如果锁释放干净了,走这个逻辑
	// 获取到头节点
        Node h = head;
	// 头节点不为null
	// 如果头节点状态不为0(为-1),说明后面有点排队的Node,并且线程已经挂起了
        if (h != null && h.waitStatus != 0)
	     // 唤醒排队的线程
            unparkSuccessor(h);
        return true;
    }
    return false;
}
  • tryRelease(int releases)
protected final boolean tryRelease(int releases) {
    // 判断当前线程释放是否是持有锁资源线程
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    // 获取state值-1
    int nextc = getState() - releases;
    // 判断低16位结果是否为0,是0代表锁是否干净
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
	// 将持有锁线程设置为null
        setExclusiveOwnerThread(null);
    // 设置给state
    setState(nextc);
    // 释放干净,返回true,没释放干净返回false
    return free;
}

四、读锁分析

4.1 读锁加锁流程概述

4.1.1 基本读锁流程

image.png

读锁加锁流程

  1. 读操作线程,竞争读锁资源
  2. 会竞争共享锁的资源
  3. 拿到state
  4. 判断state中的低16位是否为0
    1. 如果不为0,代表写锁占用着资源如果有资源占用着写锁,但是不是当前线程,结束(写锁 - 读锁的降级)
  5. 拿到state高16位的值
    1. 公平锁:如果有人排队,直接去排队
    2. 非公平锁:查看AQS的队列中,是否写线程在排队,如果有,就去排队
  6. CAS对state的高16位+1,成功,拿到读锁资源
  7. 读锁记录锁重入的次数,需要让每个读线程用ThreadLock存储重入次数,ReentrantReadWriteLock对读锁重入做了一些优化

记录重入次数的核心
ReentrantReadWriteLock在内部对ThreadLock做了封装,基于HoldCount对象存储重入次数,在内部有个count属性记录,而且每个线程都是自己的ThreadLocalHoldCounter,所以可以直接对内部的count进行++操作

  1. 第一个获取读锁资源的重入次数记录方式
    第一个拿到读锁资源的线程,不需要通过ThreadLock存储,内部提供了两个属性来记录第一个拿到读锁资源线程信息
    内部提供了firstReader记录第一个拿到读锁资源的线程,fistReaderHoldCount记录fistReader的锁重入次数
  2. 最后一个获取读锁资源的重入次数方式
    最后一个拿到读锁资源的线程,也会缓存他的重入次数,这样++起来方便,基于cacheHoldCounter缓存最后一个拿到锁资源线程的重入次数

锁重入流程

  1. 判断当前线程是否是第一个拿到读锁资源的:如果是,直接将firstReader以及firstReaderHoldCount设置为当前线程的信息
  2. 判断当前线程是否是firstReader:如果是,直接对firstReaderHoldCount++即可
  3. 和firstReader没有关系,先获取cachedHoldCounter判断是否是当前线程
    1. 如果不是,获取当前线程的重入次数,将cachedHoldCounter设置为当前线程
    2. 如果是,判断当前重入次数是否为0,重新设置当前线程的锁重入信息到readHolds(ThreadLocal)中,算是初始化操作,重 入次数是0
  4. 前面两者最后做了count++

读线程在AQS队列获取锁资源的后续操作

  1. 正常如果都是读线程来获取读锁资源,不需要使用到AQS队列的,直接CAS操作即可
  2. 如果写线程持有写锁,这时候读线程需要进入到AQS队列排队,可能会有多个读线程在AQS中

当写锁资源是否后,会唤醒head后面的读线程,当head后面的读线程拿到锁资源后,还需要查看next节点是否也是读线程阻塞,如果是,直接唤醒

4.1.2 源码分析

  • lock()
public void lock() {
     // 读锁加锁方法入口
    sync.acquireShared(1);
}
  • acquireShared(int arg)
public final void acquireShared(int arg) {
    // 竞争锁资源,返回-1说明没有拿到锁资源
    if (tryAcquireShared(arg) < 0)
        // 没有拿到锁资源,去排队
        doAcquireShared(arg);
}
  • tryAcquireShared(int unused)
// 读锁竞争锁资源核心流程
protected final int tryAcquireShared(int unused) {
    // 拿到当前线程
    Thread current = Thread.currentThread();
    // 拿到state
    int c = getState();
    // 拿到state的低16位,判断!=0,说明有写锁占用锁资源
    // 并且如果持有锁线程不是当前线程,无法获取锁资源,返回-1,去排队
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    // 没有线程持有写锁
    // 获取读锁的信心,state的高16位
    int r = sharedCount(c);
    // 公平锁:就查看队列是否有排队的,有排队的,就直接结束
    // 非公平锁:没有排队的,直接抢,有排队的,但是读锁其实不用排队的,如果出现这种情况,大部分是写锁资源刚刚释放
    if (!readerShouldBlock() &&
        // 判断持有读锁的临界值释放达到
        r < MAX_COUNT &&
        // CAS修改state,对高16位进行+1
        compareAndSetState(c, c + SHARED_UNIT)) {
        // r==0,当前是第一个线程拿到读锁资源的线程
        if (r == 0) {
            // 将firstReader设置位当前线程
            firstReader = current;
            // 将count设置为1
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            // firstReader == current 判断当前线程是否是第一个获取读锁资源的线程
            // 是直接++
            firstReaderHoldCount++;
        } else {
            // 到这说明不是第一个线程获取读锁的线程
            // 获取到最后一个拿到读锁资源的线程
            HoldCounter rh = cachedHoldCounter;
            // 判断当前线程是否是最后一个拿到读锁资源的线程
            if (rh == null || rh.tid != getThreadId(current))
                // 如果不是,设置cachedHoldCounter为当前线程
                cachedHoldCounter = rh = readHolds.get();
            // 当前线程是之前的最后一个线程cachedHoldCounter
            else if (rh.count == 0)
                // 将当前的重入信息设置到ThreadLocal中
                readHolds.set(rh);
            // 重入次数++
            rh.count++;
        }
        // 获取锁成功返回1
        return 1;
    }
    // 如果上面没有拿到锁资源,尝试在获取一次
    return fullTryAcquireShared(current);
}
  • sharedCount(int c)
// state无符号右移动16位
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
  • readerShouldBlock()
// 公平锁,查看队列是否有人排队
final boolean readerShouldBlock() {
    return hasQueuedPredecessors();
}
  • hasQueuedPredecessors()
public final boolean hasQueuedPredecessors() {
    // 尾节点
    Node t = tail;
    // 头节点
    Node h = head;
    Node s;
   // 条件1 h != t
   // 当h == t,则 h!=t 返回false
   // 条件2 ((s = h.next) == null || s.thread != Thread.currentThread())
   //  s 为头节点下一个节点 
   //  s 节点不为null,并且是节点的线程为当前线程(排在第一名的是不是我)
   // 当 头节点下一个节点的线程 == 当前线程,则 s.thread != Thread.currentThread() 返回false
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}
  • readerShouldBlock()
// 非公平锁
final boolean readerShouldBlock() {
    return apparentlyFirstQueuedIsExclusive();
}
  • apparentlyFirstQueuedIsExclusive()
// 非公平锁的判断
final boolean apparentlyFirstQueuedIsExclusive() {
    Node h, s;
    // && 只要🈶一个条件为 false就可以抢锁 !readerShouldBlock(
    // head 是 null 可以直接抢占锁资源
    return (h = head) != null &&
        // head的后继节点为null,可以直接抢占锁资源
        (s = h.next)  != null &&
        // head后面的是Node,是共享锁,可以直接抢占锁资源
        !s.isShared()         &&
        // head后面排队的thread为null,可以直接抢占锁资源
        s.thread != null;
}
  • isShared()
// 判断是否是共享锁
final boolean isShared() {
    return nextWaiter == SHARED;
}
  • fullTryAcquireShared(Thread current)
// 如果上面没有拿到锁资源,尝试在获取一次
final int fullTryAcquireShared(Thread current) {
    // 声明当前线程重入次数
    HoldCounter rh = null;
    for (;;) {
        // 拿到state的值
        int c = getState();
        // 如果有写锁占用锁资源,并且不是当前线程,返回-1,走排队策略
        if (exclusiveCount(c) != 0) {
            if (getExclusiveOwnerThread() != current)
                return -1;
        } 
        // 查看当前释放可以尝试竞争锁资(公平锁和非公平锁的逻辑)
        else if (readerShouldBlock()) {
            // 无论公平还是非公平,只要进来,就代表着放到AQS队列中了,先做一波准备
            // 在处理ThreadLocal的内存泄漏问题
            if (firstReader == current) {
                // 如果当前线程是之前的firstReader,什么都不需要做
                // assert firstReaderHoldCount > 0;
            } else {
                // 第一次进来的是NULL
                if (rh == null) {
                    // 拿到最后一个获取读锁的线程
                    rh = cachedHoldCounter;
                    // 当前线程并不是cachedHoldCounter,没有拿到
                    if (rh == null || rh.tid != getThreadId(current)) {
                        // 从自己的ThreadLocal中拿到重入次数计数器
                        rh = readHolds.get();
                        // 如果计数器为0,说明之前没有拿到过读锁资源
                        if (rh.count == 0)
                            // remove,避免内存泄漏
                            readHolds.remove();
                    }
                }
                // 前面处理完事之后,直接返回-1
                if (rh.count == 0)
                    return -1;
            }
        }
        // 判断重入次数,是否超出预知
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // CAS尝试获取锁
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            // state高16位==0,说明第一次抢到读锁
            if (sharedCount(c) == 0) {
                // 设置第一个拿到读锁的线程
                firstReader = current;
                // 第一个读锁线程重入次数设置为1
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                // 当前线程是第一次拿到读锁的线程,重入次数++
                firstReaderHoldCount++;
            } else {
                // rh==null,获取到最后一个拿到读锁线程
                if (rh == null)
                    rh = cachedHoldCounter;
                // 前线程并不是cachedHoldCounter
                if (rh == null || rh.tid != getThreadId(current))
                    // 从ThreadLocal中获取
                    rh = readHolds.get();
                // 设置当前线程为最后一个拿到读锁的线程并经++
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
                cachedHoldCounter = rh; // cache for release
            }
            return 1;
        }
    }
}
  • doAcquireShared(int arg) 进AQS队列排队
// 读锁需要排队的操作
private void doAcquireShared(int arg) {
    // 声明Node,类型是共享锁,并且扔到AQS中排队
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // 拿到上一个节点
            final Node p = node.predecessor();
            // 如果前继节点是头节点,直接执行tryAcquireShared
            if (p == head) {
                // -1 抢锁是不,1拿锁成功设置头信息
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    // 拿到读锁信息后,需要做后续处理
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            // 找到prev有效节点,将状态设置-1,挂起线程
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
  • setHeadAndPropagate(Node node, int propagate)
private void setHeadAndPropagate(Node node, int propagate) {
    // 拿到head节点
    Node h = head; // Record old head for check below
    // 将当前节点设置为head节点
    setHead(node);
    // propagate=1,第一个判断更多的是在信号量有处理JDK1.5bug
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        // 拿到当前节点
        Node s = node.next;
        // 如果next节点是共享锁,直接唤醒next节点
        if (s == null || s.isShared())
            doReleaseShared();
    }
}
  • doReleaseShared()
// 唤醒AQS中排队的读线程
private void doReleaseShared() {
    for (;;) {
        // 拿到头节点
        Node h = head;
        // 头节点不为null,说明有排队的
        if (h != null && h != tail) {
            // 拿到head的状态
            int ws = h.waitStatus;
            // 判断是否为-1
            if (ws == Node.SIGNAL) {
                // 到这,说明后面有挂起的线程,先基于CAS将head的状态-1,修改为0
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                // 唤醒后面的节点
                unparkSuccessor(h);
            }
            // 不是读写锁准备的,在信号量在说
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        // 出口
        if (h == head)                   // loop if head changed
            break;
    }
}

4.2 读锁的释放流程

4.2.1 释放锁流程

1、处理重入以及state的值

2、唤醒后续排队的Node

4.2.2 源码分析

  • unlock()
public void unlock() {
    // 读锁释放入口
    sync.releaseShared(1);
}
  • releaseShared(int arg)
public final boolean releaseShared(int arg) {
    // tryReleaseShared 释放锁的核心流程
    if (tryReleaseShared(arg)) {
        // 唤醒在AQS排队中的读线程
        doReleaseShared();
        return true;
    }
    return false;
}
  • tryReleaseShared(int unused)
protected final boolean tryReleaseShared(int unused) {
    // 拿到当前线程
    Thread current = Thread.currentThread();
    // 如果是第一个拿到的读锁的线程,直接进行--,不需要ThreadLocal
    if (firstReader == current) {
        // assert firstReaderHoldCount > 0;
        // 重入次数等于1,将firstReader设置为空
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            // --操作
            firstReaderHoldCount--;
    } else {
        // 不是第一个拿到读锁的线程,从cachedHoldCounter一集ThreadLocal处理
        HoldCounter rh = cachedHoldCounter;
        // 如果不是最后一个拿到读锁的线程,就从ThreadLocal中拿
        if (rh == null || rh.tid != getThreadId(current))
            // 从ThreadLocal中拿
            rh = readHolds.get();
        // 获取到count值
        int count = rh.count;
        // 如果重入次数小于等于1
        if (count <= 1) {
            // 从ThreadLocal中异常
            readHolds.remove();
            // 如果已经是0,没必要在unlock,抛出一昂
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        // 重入次数--
        --rh.count;
    }
    for (;;) {
        // 拿到state,高16位,-1,成功返回state是否为0
        int c = getState();
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc))
            // 等于0说明锁释放干净了
            return nextc == 0;
    }
}
  • doReleaseShared()
// 唤醒AQS中排队的读线程
private void doReleaseShared() {
    for (;;) {
        // 拿到头节点
        Node h = head;
        // 头节点不为null,说明有排队的
        if (h != null && h != tail) {
            // 拿到head的状态
            int ws = h.waitStatus;
            // 判断是否为-1
            if (ws == Node.SIGNAL) {
                // 到这,说明后面有挂起的线程,先基于CAS将head的状态-1,修改为0
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                // 唤醒后面的节点
                unparkSuccessor(h);
            }
            // 不是读写锁准备的,在信号量在说
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        // 出口
        if (h == head)                   // loop if head changed
            break;
    }
}

end~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值