JUC探险-10、常见工具、数据结构

18 篇文章 0 订阅

一、:LockSupport

  ①LockSupport简介

    在AQSReentrantLockReentrantReadWriteLockCondition中,我们经常能看到LockSupport.park()方法和LockSupport.unpark()方法的调用。
    LockSupprot是线程的阻塞原语,用来阻塞和唤醒线程。每个使用LockSupport的线程都会与一个许可permit)关联,如果该许可可用,并且可在线程中使用,调用park()将会立即返回,否则可能阻塞。如果许可尚不可用,可以调用unpark()使其可用。但是注意许可不可重入,也就是说只能调用一次park()方法,否则会一直阻塞。

  ②LockSupport中的方法

    所有方法,根据作用不同,大体可以分为两类:阻塞和唤醒。

    1、阻塞方法

      ●void park():阻塞当前线程,如果调用unpark方法或者当前线程被中断,从能从park()方法中返回。
      ●void park(Object blocker):功能同上,入参增加一个Object对象,用来记录导致线程阻塞的阻塞对象,方便进行问题排查。
      ●void parkNanos(long nanos):阻塞当前线程,最长不超过nanos纳秒,增加了超时返回的特性。
      ●void parkNanos(Object blocker, long nanos):功能同上,入参增加一个Object对象,用来记录导致线程阻塞的阻塞对象,方便进行问题排查。
      ●void parkUntil(long deadline):阻塞当前线程,知道deadline时间。
      ●void parkUntil(Object blocker, long deadline):功能同上,入参增加一个Object对象,用来记录导致线程阻塞的阻塞对象,方便进行问题排查。

    2、唤醒方法

      ●void unpark(Thread thread):唤醒处于阻塞状态的指定线程。

    3、细节分析

      每个线程都有一个许可(permit),permit只有两个值1和0(默认是0-不可用)。
        ●当调用unpark()方法,就会将thread线程的许可permit设置成1(注意多次调用unpark方法,不会累加,permit值还是1)。
        ●当调用park()方法,如果当前线程的permit是1,那么将permit设置为0,并立即返回。如果当前线程的permit是0,那么当前线程就会阻塞,直到别的线程将当前线程的permit设置为1,park方法会将permit再次设置为0,并返回。

      注意:
        ●因为permit默认是0,所以一开始调用park()方法,线程必定会被阻塞。调用unpark(thread)方法后,会自动唤醒thread线程,即park方法立即返回。
        ●park系列的方法就是直接阻塞当前线程的,所以可以不需要线程变量参数。而unpark()方法是唤醒对应线程的,所以必须传递线程变量thread。

      synchronzed致使线程阻塞,线程会进入到BLOCKED状态,而调用LockSupprt方法阻塞线程会致使线程进入到WAITING状态。

      对于WAITING等待状态有两种唤醒方式:
        ●调用对应的唤醒方法。这里就是LockSupport的unpark()方法。
        ●调用该线程变量的interrupt()方法,会唤醒该线程,并抛出InterruptedException异常。


二、:CountDownLatch

  ①CountDownLatch简介

      在多线程协作完成业务功能时,有时候需要等待其他多个线程完成任务之后,主线程才能继续往下执行业务功能,在这种的业务场景下,通常可以使用Thread类的join方法,让主线程等待被join的线程执行完之后,主线程才能继续往下执行。当然,使用线程间消息通信机制也可以完成。其实,java并发工具类中为我们提供了类似“倒计时”这样的工具类,可以十分方便的完成所说的这种业务场景,这就是CountDownLatch。

  ②CountDownLatch中的方法

    1、构造方法

public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}

private static final class Sync extends AbstractQueuedSynchronizer {
    Sync(int count) {
        setState(count);
    }

    int getCount() {
        return getState();
    }
}

      根据构造方法及其中的Sync的声明可以看出:
        ●CountDownLatch构造器中的count最终还是传递了ASQ中的state
        ●CountDownLatch是采用共享锁来实现的。

    2、await()方法

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // 如果计数器值不等于0,则会调用doAcquireSharedInterruptibly()方法
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

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

      方法作用主要是使当前线程在锁存器倒计数到达0之前一直等待,除非线程被中断。根据其中的判断条件不难看出,如果计数器值不等于0,则会调用doAcquireSharedInterruptibly()方法。

      ⅰ、doAcquireSharedInterruptibly()
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
            	// 根据前面的方法可以看出,如果计数器值不等于0,那么r会一直小于0
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

        方法作用主要是自旋尝试获取同步状态(直至计数器值为0或被中断)。

    3、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;
    }
}

      方法作用主要是递减锁存器的计数。如果计数到达0,则释放所有等待的线程。其中的doReleaseShared()是使用的AQS中的方法。同时,我们可以看出,当调用countDown()方法后,当前线程是不会被阻塞的,会继续往下执行。


三、:CyclicBarrier

  ①CyclicBarrier简介

    CyclicBarrier也是一种多线程并发控制的实用工具,和CountDownLatch一样具有等待计数的功能,但是相比于CountDownLatch功能更加强大。它们之间最大的区别在于CyclicBarrier的计数器可以重置,相当于可以循环使用。

  ②CyclicBarrier中的方法

    1、构造方法

      CyclicBarrier的内部是使用重入锁ReentrantLockCondition。它有两个构造函数:
        ●CyclicBarrier(int parties):创建一个新的yiCyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动barrier时执行预定义的操作。
        ●CyclicBarrier(int parties, Runnable barrierAction) :创建一个新的CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动barrier时执行给定的屏障操作,该操作由最后一个进入barrier的线程执行。

public CyclicBarrier(int parties) {
    this(parties, null);
}

public CyclicBarrier(int parties, Runnable barrierAction) {
    if (parties <= 0) throw new IllegalArgumentException();
    // parties表示拦截线程的数量
    this.parties = parties;
    this.count = parties;
    // barrierAction为CyclicBarrier接收的Runnable命令,用于在线程到达屏障时,优先执行barrierAction,用于处理更加复杂的业务场景
    this.barrierCommand = barrierAction;
}

    2、await()方法

public int await() throws InterruptedException, BrokenBarrierException {
    try {
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen
    }
}

      await()方法作用类比CountDownLatch,是在所有参与者都已经在此barrier上调用await()方法之前,将一直等待。其核心实现在dowait()方法上。

      ⅰ、dowait()
private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException {
	// 获取锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    	// 分代
        final Generation g = generation;

		// 当前generation“已损坏”,抛出BrokenBarrierException异常(抛出该异常一般都是某个线程在等待某个处于“断开”状态的CyclicBarrie)
        if (g.broken)
            throw new BrokenBarrierException();

		// 如果线程中断,终止CyclicBarrier
        if (Thread.interrupted()) {
            breakBarrier();
            throw new InterruptedException();
        }

		// 进来一个线程,将count减1
        int index = --count;
        // 如果count为0,表示所有线程均已到位,触发Runnable任务
        if (index == 0) {  // tripped
            boolean ranAction = false;
            try {
                final Runnable command = barrierCommand;
                // 触发任务
                if (command != null)
                    command.run();
                ranAction = true;
                // 唤醒所有等待线程,并更新generation
                nextGeneration();
                return 0;
            } finally {
                if (!ranAction)
                    breakBarrier();
            }
        }

        // loop until tripped, broken, interrupted, or timed out
        for (;;) {
            try {
            	// 如果不是超时等待,则调用Condition.await()方法等待
                if (!timed)
                    trip.await();
                else if (nanos > 0L)
                	// 如果是超时等待,调用Condition.awaitNanos()方法等待
                    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();

			// generation已经更新,返回index
            if (g != generation)
                return index;

			// 如果是超时等待,并且时间已到,终止CyclicBarrier,并抛出异常
            if (timed && nanos <= 0L) {
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
    	// 释放锁
        lock.unlock();
    }
}

        代码整体长度比较长,但是做的事情比较单纯,就是如果该线程不是到达的最后一个线程,则该线程会一直处于等待状态。其中会有一些跳出的情况:
          ●最后一个线程到达。(即index == 0)
          ●超出了指定时间。(超时等待的情况)
          ●其他的某个线程中断了当前线程。
          ●其他的某个线程中断了另一个等待的线程。
          ●其他的某个线程在等待barrier时超时。(超时等待的情况)
          ●其他的某个线程在此barrier调用reset()方法。(reset()方法用于将屏障重置为初始状态)

    3、breakBarrier()方法

      代码中,Generation描述着CyclicBarrier的更显换代。在CyclicBarrier中,同一批线程属于同一代。当有parties个线程到达barrier,generation就会被更新换代。其中broken标识该当前CyclicBarrier是否已经处于中断状态。默认barrier是没有损坏的。

private static class Generation {
    boolean broken = false;
}

      当barrier损坏了或者有一个线程中断了,则通过breakBarrier()来终止所有的线程:

private void breakBarrier() {
    generation.broken = true;
    count = parties;
    trip.signalAll();
}

      在breakBarrier()中除了将broken设置为true,还会调用signalAll将在CyclicBarrier处于等待状态的线程全部唤醒。

    4、nextGeneration()方法

      当所有线程都已经到达barrier处(index == 0),则会通过nextGeneration()进行更新换代操作,在这个步骤中,做了三件事:唤醒所有线程、重置count、更新generation。

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

    5、reset()方法

public void reset() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        breakBarrier();   // break the current generation
        nextGeneration(); // start a new generation
    } finally {
        lock.unlock();
    }
}

      根据前面的说明,可以看到此方法主要作用是将屏障重置为初始状态。如果当前有线程正在临界点等待的话,将抛出BrokenBarrierException。

  ③CountDownLatch VS CyclicBarrier

    通过上面的分析,可以得出CountDownLatch和CyclicBarrier的不同侧重点:
      ●CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行。
CountDownLatch强调一个线程等多个线程完成某件事情。CyclicBarrier是多个线程互等,等大家都完成,再携手共进。
      ●调用CountDownLatch的countDown()方法后,当前线程并不会阻塞,会继续往下执行;而调用CyclicBarrier的await()方法,会阻塞当前线程,直到CyclicBarrier指定的线程全部都到达了指定点的时候,才能继续往下执行。
      ●CountDownLatch方法比较少,操作比较简单,而CyclicBarrier提供的方法更多,比如能够通过getNumberWaiting(),isBroken()这些方法获取当前多个线程的状态,并且CyclicBarrier的构造方法可以传入barrierAction,指定当所有线程都到达时执行的业务功能。
      ●CountDownLatch是不能复用的,而CyclicLatch是可以复用的。


四、:Semaphore

  ①Semaphore简介

    Semaphore可以理解为信号量,用于控制资源能够被并发访问的线程数量,以保证多个线程能够合理的使用特定资源。Semaphore就相当于一个许可证,线程需要先通过acquire()方法获取该许可证,该线程才能继续往下执行,否则只能在该方法处阻塞等待。当执行完业务功能后,需要通过release()方法将许可证归还,以便其他线程能够获得许可证继续执行。
    Semaphore可以用于做流量控制,特别是公共资源有限的应用场景,比如数据库连接。假如有多个线程读取数据后,需要将数据保存在数据库中,而可用的最大数据库连接只有10个,这时候就需要使用Semaphore来控制能够并发访问到数据库连接资源的线程个数最多只有10个。在限制资源使用的应用场景下,Semaphore是特别合适的。

  ②Semaphore中的方法

    1、构造方法

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

public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

      可以看到,在Semaphore的构造方法中还支持指定是否具有公平性,默认的是非公平,这样也是为了保证吞吐量。
      同时,特殊情况下,当信号量Semaphore = 1时,它可以当作互斥锁使用。其中0、1就相当于它的状态,当=1时表示其他线程可以获取;当=0时,排他,即其他线程必须要等待。

    2、acquire()方法

public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

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

      获取一个许可,默认使用的是可中断方式,如果尝试获取许可失败,会进入AQS的队列中排队。
      这里的tryAcquireShared()方法实现根据公平与非公平模式而不同。如果我们选择非公平模式,则调用NonfairSync的tryAcquireShared()方法,否则调用FairSync的tryAcquireShared()方法。
      doAcquireSharedInterruptibly()则是沿用AQS中的方法。

      ⅰ、NonfairSync的tryAcquireShared()
protected int tryAcquireShared(int acquires) {
    return nonfairTryAcquireShared(acquires);
}

final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
    	// 获取当前的信号量许可
        int available = getState();
        // 计算剩余的信号量许可数
        int remaining = available - acquires;
        // CAS设置信号量
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}
      ⅱ、FairSync的tryAcquireShared()
protected int tryAcquireShared(int acquires) {
    for (;;) {
    	// 判断该线程是否位于同步队列的头
        if (hasQueuedPredecessors())
            return -1;
        // 获取当前的信号量许可
        int available = getState();
        // 计算剩余的信号量许可数
        int remaining = available - acquires;
        // CAS设置信号量
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

    3、release()方法

public void release() {
    sync.releaseShared(1);
}

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

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");
        // 设置可获取的信号许可数为next
        if (compareAndSetState(current, next))
            return true;
    }
}

      释放一个许可,释放一个许可时state的值会加1,并且会唤醒下一个等待获取许可的线程。


五、:Exchanger

  ①Exchanger简介

    Exchanger是一个用于线程间协作的工具类,使得两个线程间能够交换数据。它提供了一个交换的同步点,在这个同步点两个线程能够交换数据。具体交换数据是通过exchange()方法来实现的,如果一个线程先执行exchange()方法,那么它会同步等待另一个线程也执行exchange()方法,这个时候两个线程就都达到了同步点,两个线程就可以交换数据。

  ②Exchanger中的方法

    Exchanger是几个工具类中最简单的也是最复杂的,简单在于API非常简单,只有一个构造方法和两个exchange()方法,最复杂在于它的实现是最复杂的。
    在Exchanger中,有几个重要的成员变量:

// 为每个线程保留唯一的一个Node节点
private final Participant participant;

// 数组槽
private volatile Node[] arena;

// 单个槽
private volatile Node slot;

    1、构造方法

public Exchanger() {
    participant = new Participant();
}

static final class Participant extends ThreadLocal<Node> {
    public Node initialValue() { return new Node(); }
}

@sun.misc.Contended static final class Node {
	// arena的下标
    int index;              // Arena index
    // 上一次记录的Exchanger.bound
    int bound;              // Last recorded value of Exchanger.bound
    // 在当前bound下CAS失败的次数
    int collides;           // Number of CAS failures at current bound
    // 伪随机数,用于自旋
    int hash;               // Pseudo-random for spins
    // 这个线程的当前项,也就是需要交换的数据
    Object item;            // This thread's current item
    // 做releasing操作的线程传递的项
    volatile Object match;  // Item provided by releasing thread
    // 挂起时设置线程值,其他情况下为null
    volatile Thread parked; // Set to this thread when parked, else null
}

    2、exchange()方法

      当一个线程执行该方法的时候,会等待另一个线程也执行该方法,因此两个线程就都达到了同步点。

// 将数据交换给另一个线程,同时返回获取的数据
public V exchange(V x) throws InterruptedException {}

// 与上一个方法功能基本一样,只不过这个方法同步等待的时候,增加了超时时间
public V exchange(V x, long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {}

      exchange()方法有两个,第二个可以看作是前一个的功能升级。这里我们以主要功能,也就是第一个方法继续跟踪代码。

@SuppressWarnings("unchecked")
public V exchange(V x) throws InterruptedException {
    Object v;
    Object item = (x == null) ? NULL_ITEM : x; // translate null args
    if ((arena != null ||
         (v = slotExchange(item, false, 0L)) == null) &&
        ((Thread.interrupted() || // disambiguates null return
          (v = arenaExchange(item, false, 0L)) == null)))
        throw new InterruptedException();
    return (v == NULL_ITEM) ? null : (V)v;
}

      这个方法主要逻辑在if判断处:arena为数组槽,如果为null,则执行slotExchange()方法,否则判断线程是否中断,如果中断值抛出InterruptedException异常,没有中断则执行arenaExchange()方法。如果slotExchange()方法执行失败了就执行arenaExchange()方法,最后返回结果V。
      NULL_ITEM 为一个空节点,其实就是一个Object对象而已。

      ⅰ、slotExchange()
private final Object slotExchange(Object item, boolean timed, long ns) {
	// 获取当前线程的节点
    Node p = participant.get();
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 如果线程中断,直接返回
    if (t.isInterrupted()) // preserve interrupt status so caller can recheck
        return null;

	// 自旋
    for (Node q;;) {
        if ((q = slot) != null) {
        	// 尝试CAS替换
            if (U.compareAndSwapObject(this, SLOT, q, null)) {
            	// 当前线程的项,也就是需要交换的数据
                Object v = q.item;
                // 做release操作的线程传递的项
                q.match = item;
                // 挂起时设置线程值
                Thread w = q.parked;
                // 挂起线程不为null,线程挂起
                if (w != null)
                    U.unpark(w);
                return v;
            }
            // 如果CAS操作失败了,则创建arena
            // bound是上次Exchanger.bound
            // create arena on contention, but continue until slot null
            if (NCPU > 1 && bound == 0 &&
                U.compareAndSwapInt(this, BOUND, 0, SEQ))
                arena = new Node[(FULL + 2) << ASHIFT];
        }
        // 如果arena不为null,直接返回,进入arenaExchange逻辑处理
        else if (arena != null)
            return null; // caller must reroute to arenaExchange
        else {
            p.item = item;
            if (U.compareAndSwapObject(this, SLOT, null, p))
                break;
            p.item = null;
        }
    }

	// 等待release,自旋并阻塞
    // await release
    int h = p.hash;
    long end = timed ? System.nanoTime() + ns : 0L;
    int spins = (NCPU > 1) ? SPINS : 1;
    Object v;
    while ((v = p.match) == null) {
        if (spins > 0) {
            h ^= h << 1; h ^= h >>> 3; h ^= h << 10;
            if (h == 0)
                h = SPINS | (int)t.getId();
            else if (h < 0 && (--spins & ((SPINS >>> 1) - 1)) == 0)
                Thread.yield();
        }
        else if (slot != p)
            spins = SPINS;
        else if (!t.isInterrupted() && arena == null &&
                 (!timed || (ns = end - System.nanoTime()) > 0L)) {
            U.putObject(t, BLOCKER, this);
            p.parked = t;
            if (slot == p)
                U.park(false, ns);
            p.parked = null;
            U.putObject(t, BLOCKER, null);
        }
        else if (U.compareAndSwapObject(this, SLOT, p, null)) {
            v = timed && ns <= 0L && !t.isInterrupted() ? TIMED_OUT : null;
            break;
        }
    }
    U.putOrderedObject(p, MATCH, null);
    p.item = null;
    p.hash = h;
    return v;
}

        ●程序首先通过participant获取当前线程节点Node。接着检测是否中断,如果中断则返回null,等待后续抛出InterruptedException异常。
        ●如果slot不为null,则进行slot消除,成功直接返回数据V,否则失败,则创建arena消除数组。
        ●如果slot为null,但arena不为null,则返回null,进入arenaExchange逻辑。
        ●如果slot为null,且arena也为null,则尝试占领该slot,失败重试,成功则跳出循环进入spin+block(自旋+阻塞)模式。

        ●在自旋+阻塞模式中,首先取得结束时间和自旋次数。
          如果match(做release操作的线程传递的项)为null,其首先尝试spins+随机次自旋(改自旋使用当前节点中的hash,并改变之)和退让。
          当自旋数为0后,如果slot发生了改变(slot != p)则重置自旋数并重试。
          否则如果:当前未中断 且 arena为null 且 (当前不是限时版本或者限时版本+当前时间未结束):阻塞或者限时阻塞。
          否则如果:当前中断 或者 arena不为null 或者 (当前为限时版本+时间已经结束):不限时版本:置v为null;限时版本:如果时间结束以及未中断则TIMED_OUT;否则给出null(原因是探测到arena非空或者当前线程中断)。
          match不为空时跳出循环。

        slot为单个槽,arena为数组槽。他们都是Node类型。在这里可能会感觉到疑惑,slot作为Exchanger交换数据的场景,应该只需要一个就可以了啊?为何还多了一个Participant和数组类型的arena呢?
        一个slot交换场所原则上来说应该是可以的,但实际情况却不是如此,多个参与者使用同一个交换场所时,会存在严重伸缩性问题。既然单个交换场所存在问题,那么我们就安排多个,也就是数组arena。通过数组arena来安排不同的线程使用不同的slot来降低竞争问题,并且可以保证最终一定会成对交换数据。但是Exchanger不是一来就会生成arena数组来降低竞争,只有当产生竞争时才会生成arena数组。那么怎么将Node与当前线程绑定呢?答案就是Participant,Participant的作用就是为每个线程保留唯一的一个Node节点,它继承ThreadLocal,同时在Node节点中记录在arena中的下标index。

      ⅱ、arenaExchange()
    private final Object arenaExchange(Object item, boolean timed, long ns) {
        Node[] a = arena;
        Node p = participant.get();
        for (int i = p.index;;) {                      // access slot at i
            int b, m, c; long j;                       // j is raw array offset
            Node q = (Node)U.getObjectVolatile(a, j = (i << ASHIFT) + ABASE);
            if (q != null && U.compareAndSwapObject(a, j, q, null)) {
                Object v = q.item;                     // release
                q.match = item;
                Thread w = q.parked;
                if (w != null)
                    U.unpark(w);
                return v;
            }
            else if (i <= (m = (b = bound) & MMASK) && q == null) {
                p.item = item;                         // offer
                if (U.compareAndSwapObject(a, j, null, p)) {
                    long end = (timed && m == 0) ? System.nanoTime() + ns : 0L;
                    Thread t = Thread.currentThread(); // wait
                    for (int h = p.hash, spins = SPINS;;) {
                        Object v = p.match;
                        if (v != null) {
                            U.putOrderedObject(p, MATCH, null);
                            p.item = null;             // clear for next use
                            p.hash = h;
                            return v;
                        }
                        else if (spins > 0) {
                            h ^= h << 1; h ^= h >>> 3; h ^= h << 10; // xorshift
                            if (h == 0)                // initialize hash
                                h = SPINS | (int)t.getId();
                            else if (h < 0 &&          // approx 50% true
                                     (--spins & ((SPINS >>> 1) - 1)) == 0)
                                Thread.yield();        // two yields per wait
                        }
                        else if (U.getObjectVolatile(a, j) != p)
                            spins = SPINS;       // releaser hasn't set match yet
                        else if (!t.isInterrupted() && m == 0 &&
                                 (!timed ||
                                  (ns = end - System.nanoTime()) > 0L)) {
                            U.putObject(t, BLOCKER, this); // emulate LockSupport
                            p.parked = t;              // minimize window
                            if (U.getObjectVolatile(a, j) == p)
                                U.park(false, ns);
                            p.parked = null;
                            U.putObject(t, BLOCKER, null);
                        }
                        else if (U.getObjectVolatile(a, j) == p &&
                                 U.compareAndSwapObject(a, j, p, null)) {
                            if (m != 0)                // try to shrink
                                U.compareAndSwapInt(this, BOUND, b, b + SEQ - 1);
                            p.item = null;
                            p.hash = h;
                            i = p.index >>>= 1;        // descend
                            if (Thread.interrupted())
                                return null;
                            if (timed && m == 0 && ns <= 0L)
                                return TIMED_OUT;
                            break;                     // expired; restart
                        }
                    }
                }
                else
                    p.item = null;                     // clear offer
            }
            else {
                if (p.bound != b) {                    // stale; reset
                    p.bound = b;
                    p.collides = 0;
                    i = (i != m || m == 0) ? m : m - 1;
                }
                else if ((c = p.collides) < m || m == FULL ||
                         !U.compareAndSwapInt(this, BOUND, b, b + SEQ + 1)) {
                    p.collides = c + 1;
                    i = (i == 0) ? m : i - 1;          // cyclically traverse
                }
                else
                    i = m + 1;                         // grow
                p.index = i;
            }
        }
    }

        首先通过participant取得当前节点Node,然后根据当前节点Node的index去取arena中相对应的节点node。
        前面提到过arena可以确保不同的slot在arena中是不会相冲突的,那么是怎么保证的呢?我们先看arena的创建:(见slotExchange()代码中)

arena = new Node[(FULL + 2) << ASHIFT];

static final int FULL = (NCPU >= (MMASK << 1)) ? MMASK : NCPU >>> 1;

private static final int ASHIFT = 7;

        然后通过以下代码取得在arena中的节点:

Node q = (Node)U.getObjectVolatile(a, j = (i << ASHIFT) + ABASE);

ABASE = U.arrayBaseOffset(ak) + (1 << ASHIFT);

Class<?> ak = Node[].class;

        U.arrayBaseOffset获取对象头长度,数组元素的大小可以通过unsafe.arrayIndexScale() 方法获取到。这也就是说要访问第N个元素的话,你的偏移量offset应该是arrayOffset+N*arrayScale。
        其次我们回顾Node节点的定义,在Java 8中我们利用sun.misc.Contended来规避伪共享。所以说通过 << ASHIFT方式加上sun.misc.Contended,所以使得任意两个可用Node不会再同一个缓存行中。

        在Node定义中有两个变量值得思考:bound以及collides。前面提到了数组area是为了避免竞争而产生的,如果系统不存在竞争问题,那么完全没有必要开辟一个高效的arena来徒增系统的复杂性。首先通过单个slot的exchanger来交换数据,当探测到竞争时将安排不同的位置的slot来保存线程Node,并且可以确保没有slot会在同一个缓存行上。如何来判断会有竞争呢?CAS替换slot失败,如果失败,则通过记录冲突次数来扩展arena的尺寸,我们在记录冲突的过程中会跟踪“bound”的值,以及会重新计算冲突次数在bound的值被改变时。

        ●我们再次回到arenaExchange()。取得arena中的node节点后,如果定位的节点q不为空,且CAS操作成功,则交换数据,返回交换的数据,唤醒等待的线程。
          如果q为null且下标在bound & MMASK范围之内,则尝试占领该位置,如果成功,则采用自旋 + 阻塞的方式进行等待交换数据。
          如果下标不在bound & MMASK范围之内 或者 由于q不为null但是竞争失败的时候:消除p。
        ●加入bound 不等于当前节点的bond(b != p.bound),则更新p.bound = b,collides = 0,i = m 或者 m – 1。
        ●如果冲突的次数不到m 或者 m已经为最大值 或者 修改当前bound的值失败,则通过增加一次collides以及循环递减下标i的值;否则更新当前bound的值成功:我们令i为m+1即为此时最大的下标。最后更新当前index的值。

系列文章传送门:

JUC探险-1、初识概貌
JUC探险-2、synchronized
JUC探险-3、volatile
JUC探险-4、final
JUC探险-5、原子类
JUC探险-6、Lock & AQS
JUC探险-7、ReentrantLock
JUC探险-8、ReentrantReadWriteLock
JUC探险-9、Condition
JUC探险-10、常见工具、数据结构
JUC探险-11、ConcurrentHashMap
JUC探险-12、CopyOnWriteArrayList
JUC探险-13、ConcurrentLinkedQueue
JUC探险-14、ConcurrentSkipListMap
JUC探险-15、BlockingQueue
JUC探险-16、ThreadLocal
JUC探险-17、线程池

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值