并发工具类(二)

AQS

java.util.concurrent.locks.AbstractQueuedSynchronizer
java.util.concurrent.locks包基于AQS实现
1、抽象队列同步器简称AQS,它同步器的基础组件,JUC种锁的底层实现均依赖于AQS,开发不需要使用(了解原理和构成)。。
2、采用FIFO的双向队列实现,队列元素为Node(静态内部类),Node内的thread变量用于存储进入队列的线程。
3、Node节点内部的SHARED用来标记该线程是获取共享资源时被阻塞挂起后放入AQS队列的,
EXCLUSIVE用来标记线程是获取独占资源时被挂起后放入AQS队列的。waitStatus记录当前线程等待状态,可以为CANCELLED(线程被取消了)、SIGNAL(线程需要被唤醒)、CONDITION(线程在条件队列里面等待)、PROPAGATE(释放共享资源时需要通知其他节点) ;prev记录当前节点的前驱节点,next记录当前节点的后继节点。
4、在AQS中维持了一个单一的状态信息state,可以通过getState、setState、compareAndSetState函数修改其值。对于ReentrantLock的实现来说,state可以用来表示当前线程获取锁的可重入次数;对于读写锁ReentrantReadWriteLock来说,state的高16位表示读状态,也就是获取该读锁的次数,低16位表示获取到写锁的线程的可重入次数;对于semaphore来说,state用来表示当前可用信号的个数;对于
CountDownlatch来说,state用来表示计数器当前的值。

源码

有两个内部类Node和ConditionObject

Node源码

static final class Node {
    /** Marker to indicate a node is waiting in shared mode */
    //共享挂起
    static final Node SHARED = new Node();
    /** Marker to indicate a node is waiting in exclusive mode */
    //排他挂起
    static final Node EXCLUSIVE = null;

    /** waitStatus value to indicate thread has cancelled */
    static final int CANCELLED =  1;
    /** waitStatus value to indicate successor's thread needs unparking */
    static final int SIGNAL    = -1;
    /** waitStatus value to indicate thread is waiting on condition */
    static final int CONDITION = -2;
    /**
     * waitStatus value to indicate the next acquireShared should
     * unconditionally propagate
     */
    static final int PROPAGATE = -3;

    /**
     * Status field, taking on only the values:
     *   SIGNAL:     The successor of this node is (or will soon be)
     *               blocked (via park), so the current node must
     *               unpark its successor when it releases or
     *               cancels. To avoid races, acquire methods must
     *               first indicate they need a signal,
     *               then retry the atomic acquire, and then,
     *               on failure, block.
     *   CANCELLED:  This node is cancelled due to timeout or interrupt.
     *               Nodes never leave this state. In particular,
     *               a thread with cancelled node never again blocks.
     *   CONDITION:  This node is currently on a condition queue.
     *               It will not be used as a sync queue node
     *               until transferred, at which time the status
     *               will be set to 0. (Use of this value here has
     *               nothing to do with the other uses of the
     *               field, but simplifies mechanics.)
     *   PROPAGATE:  A releaseShared should be propagated to other
     *               nodes. This is set (for head node only) in
     *               doReleaseShared to ensure propagation
     *               continues, even if other operations have
     *               since intervened.
     *   0:          None of the above
     *
     * The values are arranged numerically to simplify use.
     * Non-negative values mean that a node doesn't need to
     * signal. So, most code doesn't need to check for particular
     * values, just for sign.
     *
     * The field is initialized to 0 for normal sync nodes, and
     * CONDITION for condition nodes.  It is modified using CAS
     * (or when possible, unconditional volatile writes).
     */
    volatile int waitStatus;

    /**
     * Link to predecessor node that current node/thread relies on
     * for checking waitStatus. Assigned during enqueuing, and nulled
     * out (for sake of GC) only upon dequeuing.  Also, upon
     * cancellation of a predecessor, we short-circuit while
     * finding a non-cancelled one, which will always exist
     * because the head node is never cancelled: A node becomes
     * head only as a result of successful acquire. A
     * cancelled thread never succeeds in acquiring, and a thread only
     * cancels itself, not any other node.
     */
    volatile Node prev;

    /**
     * Link to the successor node that the current node/thread
     * unparks upon release. Assigned during enqueuing, adjusted
     * when bypassing cancelled predecessors, and nulled out (for
     * sake of GC) when dequeued.  The enq operation does not
     * assign next field of a predecessor until after attachment,
     * so seeing a null next field does not necessarily mean that
     * node is at end of queue. However, if a next field appears
     * to be null, we can scan prev's from the tail to
     * double-check.  The next field of cancelled nodes is set to
     * point to the node itself instead of null, to make life
     * easier for isOnSyncQueue.
     */
    volatile Node next;

    /**
     * The thread that enqueued this node.  Initialized on
     * construction and nulled out after use.
     */
    //存放线程
    volatile Thread thread;

AQS中的变量

/**
 * Head of the wait queue, lazily initialized.  Except for
 * initialization, it is modified only via method setHead.  Note:
 * If head exists, its waitStatus is guaranteed not to be
 * CANCELLED.
 */
private transient volatile Node head;

/**
 * Tail of the wait queue, lazily initialized.  Modified only via
 * method enq to add new wait node.
 */
private transient volatile Node tail;

/**
 * The synchronization state.
 */
private volatile int state;

类图

在这里插入图片描述

ReentranLock原理

对于ReentranLock构造相当于构造了AQS,第一次进入时通过unsafe类修改可重入次数state(从AQS记录下来),并记录当前持有排他锁的进程,后续再获取锁的时候,判断是否是第一次获取锁,判断获取锁的进程和当前持有排他锁的是否是同一个线程,然后再修改state状态,注意可重入次数是有限的,就是state这个变量的最大值

package com.mkevin.demo8;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 通过跟踪ReentrantLock的运行, 理解AQS的原理和作用
 */
public class AQSDemo0 {

    public static void main(String[] args) throws InterruptedException {

        ReentrantLock rl = new ReentrantLock();
        System.out.println(Thread.currentThread().getName()+" start rl.lock");
        rl.lock();
        rl.lock();

        Thread th = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" start rl.lock");
                System.out.println("start re lock2");
                rl.lock();
            }
        });

        th.start();
        th.join();
        rl.unlock();
        System.out.println(Thread.currentThread().getName()+" start rl.unlock");
        System.out.println("main is over");
    }

}

打断点

image-20210227222839703

在这里插入图片描述

查看成员,ReentranLock所依赖的同步类以及公平锁和非公平锁所依赖的Fair和Nofair都是基于AQS实现的

执行完构造方法,构造完AQS

image-20210227223201186

调用lock,强制进入断点

image-20210227223457732

继续进入,调用的是NonfairSync的lock方法,想要将变量从0设置成1

image-20210227223635870

继续进入,可以看出向设置的是stateOffset位置对应的变量

image-20210227223728722

stateOffset是state字段的偏移量

在AQS中维持了一个单一的状态信息state,可以通过getState、setState、compareAndSetState函数修改其值。对于ReentrantLock的实现来说,state可以用来表示当前线程获取锁的可重入次数;对于读写锁ReentrantReadWriteLock来说,state的高16位表示读状态,也就是获取该读锁的次数,低16位表示获取到写锁的线程的可重入次数;对于semaphore来说,state用来表示当前可用信号的个数;对于
CountDownlatch来说,state用来表示计数器当前的值。

image-20210227224006620

将state变量从0设置成1后,调用setExclusiveOwnerThread方法,这个类是AQS的父类,这个方法记录了是谁持有了排他锁(本例中是main线程)

image-20210227224539779

AbstractOwnableSynchronizer只有一个变量,记录谁持有了排他锁

image-20210227224646558

这时再看ReentrantLock的状态

image-20210227225108504

第二次获取锁再进入,这时候再将state从0设置成1就会失败,程序走的是acqiure

image-20210227225242058

进入执行acquire

image-20210227225346462

进入执行tryAcquire,调用的是非公平锁

image-20210227225532235

进入nonfairTryAcquire方法

image-20210227230153444

ReentrantLock状态改变

image-20210227230311539

可以看出,可重入次数state是有限的,跟这个变量的容量有关

继续执行,th线程启动

image-20210227231909996

切换到其他线程,执行过程:设置state失败,执行acquire,获取锁失败(和持有排他锁的不是同一个线程),tryAcquire获取锁失败,加入等待队列,参数可以看出是加入一个排他锁线程,进入addWaiter方法,将线程加入队列, 进入acquireQueued方法,死循环中尝试获取锁失败,shouldParkAfterFailedAcquire前驱节点的状态为SIGNAL唤醒,然后执行park方法将当前的线程阻塞

image-20210227231949307

锁重入的状态是state=2

image-20210227232058284

强制进入方法,执行到更改state

image-20210227232301641

设置失败执行acquire方法,继续执行下去尝试获得锁

image-20210227232706243

tryAcquire获取锁失败,加入等待队列,参数可以看出是加入一个排他锁线程

image-20210227232808999

进入addWaiter方法,将线程加入队列,(如果队列为空)设置一个前驱空节点,该方法就是循环调用CAS,即使有高并发的场景,无限循环将会最终成功把当前线程追加到队尾(或设置队头)

image-20210227233318608

继续执行,thread-0线程进入队列

image-20210227233620011

返回后进入acquireQueued方法,进入一个死循环

image-20210227234200970

进入predecessor()方法

image-20210227234320299

再次tryAcquire尝试获取锁失败

image-20210227234605724

继续执行,判断获取失败之后是否需要park

image-20210227235035084

进入shouldParkAfterFailedAcquire,设置前驱节点的状态为需要通知Node.SIGNAL

image-20210227235405282

返回false,继续acquireQueued方法死循环,又执行前一次循环相同的步骤,执行到判断是否需要阻塞shouldParkAfterFailedAcquire,由于前一次设置状态为SIGNAL,所以返回true

image-20210228004125919

判断shouldParkAfterFailedAcquire为true,继续执行下一个判断

image-20210228004408889

继续执行,调用了LockSupport,它的的底层采用Unsafe类来实现,是其他同步类的阻塞与唤醒的基础。park设置阻塞,(被unpark后)interrupted清除中断标记,这样就达到了前一个线程释放锁时,执行唤醒等待锁的线程的目的

image-20210228005824198

程序继续执行到th.join();阻塞,因为只释放了一个锁

unlock方法

调用了release方法

image-20210228011808751

调用了tryRelease方法

image-20210228011937152

image-20210228012105278

回到实现类的方法,getState是2,release是1(见unlock函数传入的参数),state不为0,执行setState更新,返回false

image-20210228012233035

需要再调用一次unlock,c=0(没有线程占有排他锁),则实行setExclusiveOwnerThread(null);`把持有排他锁的线程设置为null(图中注释有误,节点不为空,节点里的线程为空)

image-20210228013325110

进入unparkSuccessor方法,这段代码的意思在于从队尾向前找出第一个可以unpark的线程,

在这里插入图片描述

在阻塞的地方重新执行死循环

image-20210228014849974

执行tryAcquire

image-20210228015327677

设置当前线程为头节点

image-20210228015531333

AQS.ConditionObject

image-20210228114519251

package com.mkevin.demo8;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 *  观察Condition的运行机制
 */
public class AQSDemo1 {

    static ReentrantLock rl = new ReentrantLock();
    static Condition condition = rl.newCondition();

    public static void main(String[] args) throws InterruptedException {

        new Runner().start();
        new Runner().start();
        new Runner().start();
    }

    static class Runner extends Thread{
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
                rl.lock();
                System.out.println(Thread.currentThread().getName()+" before await..");
                //condition.signal();
                condition.await();
                System.out.println(Thread.currentThread().getName()+" after await..");
                rl.unlock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

打上断点

image-20210228114957164

强制进入newCondition

image-20210228115030130

继续进入sync.newCondition

image-20210228115116267

进入ConditionObject

image-20210228115229987

执行的是AQS的构造器(所以本质是借助AQS的内部类实现的)
image-20210228115327876

继续执行程序

image-20210228115553174

启动三个线程,没法停住,进入await方法打断点没有效果,重启只在await处打上断点,并进入

image-20210228123344248

进入addConditionWaiter方法

image-20210228123648641

设置完后查看状态

image-20210228123728530

放入的都是线程1

image-20210228123911936

继续执行,进入park

image-20210228124024923

后续的线程会执行到nextWaiter

image-20210228133013781

添加进入了队列

image-20210228133148894

image-20210228133428126

从这里可以看出ReentrantLock.newCondition()创建的每一个Condition对象,实质上都是AQS.ConditionObject对象,而这个对象也是一个FIFO的队列

查看ConditionObject源码

public class ConditionObject implements Condition, java.io.Serializable {
    private static final long serialVersionUID = 1173984872572414699L;
    /** First node of condition queue. */
    private transient Node firstWaiter;
    /** Last node of condition queue. */
    private transient Node lastWaiter;

    /**
     * Creates a new {@code ConditionObject} instance.
     */
    public ConditionObject() { }

    // Internal methods

    /**
     * Adds a new waiter to wait queue.
     * @return its new wait node
     */
    private Node addConditionWaiter() {
        Node t = lastWaiter;
        // If lastWaiter is cancelled, clean out.
        if (t != null && t.waitStatus != Node.CONDITION) {
            unlinkCancelledWaiters();
            t = lastWaiter;
        }
        Node node = new Node(Thread.currentThread(), Node.CONDITION);
        if (t == null)
            firstWaiter = node;
        else
            t.nextWaiter = node;
        lastWaiter = node;
        return node;
    }

    /**
     * Removes and transfers nodes until hit non-cancelled one or
     * null. Split out from signal in part to encourage compilers
     * to inline the case of no waiters.
     * @param first (non-null) the first node on condition queue
     */
    private void doSignal(Node first) {
        do {
            if ( (firstWaiter = first.nextWaiter) == null)
                lastWaiter = null;
            first.nextWaiter = null;
        } while (!transferForSignal(first) &&
                 (first = firstWaiter) != null);
    }
    
    //省略...
}

查看doSignal方法中的transferForSignal方法

final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    //修改状态
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    /*
     * Splice onto queue and try to set waitStatus of predecessor to
     * indicate that thread is (probably) waiting. If cancelled or
     * attempt to set waitStatus fails, wake up to resync (in which
     * case the waitStatus can be transiently and harmlessly wrong).
     */
    //接入队列,返回前节点
    Node p = enq(node);
    int ws = p.waitStatus;
    //将前节点的状态改为SIGNAL
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

锁的分类

乐观锁/悲观锁:是否在修改之前给记录增加排它锁
公平锁/非公平锁:请求锁的时间顺序是否与获得锁的时间顺序—致。一致为公平锁,不一致为非公平锁
共享锁/独占锁:是否可以被多个线程共同持有,可以则为共享锁、不可以则为独占锁
可重入锁:一个线程再次获取它自己已经获取的锁时是否会被阻塞
自旋锁:无法获取锁时是否立刻阻塞,还是继续尝试获取指定次数

ReentrantLock:可重入锁、独占锁、悲观锁

乐观锁/悲观锁

不加锁,更新数据是对比数据的版本

1.乐观锁和悲观锁的概念来自于数据库
2.悲观锁对数据被修改持悲观态度,认为数据很容易就会被其他线程修改,所以在处理数据之前先加锁,处理完毕释放锁。
3.乐观锁对数据被修改持乐观态度,认为数据一般情况下不会被其他线程修改,所以在处理数据之前不会加锁,而是在数据进行更新时进行冲突检测。
4.对于数据库的悲观锁就是排它锁,在处理数据之前,先尝试给记录加排它锁,如果成功则继续处理,如果失败则挂起或抛出异常,直到数据处理完毕释放锁。
5.对于数据库的乐观锁所典型的就是CAS方式更新,例如:update name=kevin’where id=1 and name='kevinO’,在更新数据的时候校验这个值是否发生了变化,类似于CAS的操作。

公平锁/非公平锁

1.据线程获取锁的抢占机制,锁可以分为公平锁和非公平锁,最早请求锁的线程将最早获取到锁。而非公平锁则先请求不一定先得。JUC中的ReentrantLock提供了公平和非公平锁特性。·
2.公平锁:ReentrantLock pairLock = new ReentrantLock(true)
3.非公平锁:ReentrantLock pairLock = new ReentrantLock(false),如果构造函数不传递参数,则默认是非公平锁
4.非必要情况下使用非公平锁,公平锁存在性能开销

独占锁和共享锁

1.只能被单个线程所持有的锁是独占锁,可以被多个线程持有的锁是共享锁。
2. ReentrantLock就是以独占方式实现的,属于悲观锁
3. ReadWriteLock读锁是以共享锁方式实现的,属于乐观锁;写锁都是独占锁
4. StampedLock的写锁、悲观读锁,属于悲观锁。
5. StampedLock的乐观读锁,悲观读锁,属于乐观锁。

可重入锁

1.当一个线程想要获取本线程已经持有的锁时,不会被阻塞,而是能够再次获得这个锁,这就
是重入锁。
2. Synchornized是一种可重入锁,内部维护-
-个线程标志(谁持有锁),以及一个计数器。
3. ReentrantLock也是一种可重入锁
4.ReadWriteLock、StampedLock的读锁也是可重入锁

自旋锁

1.当获取锁的时候如果发现锁已经被其他线程占有,则不阻塞自己,也不释放CPU使用权,而是尝试多次获取,如果尝试了指定次数之后仍然没有获得锁,再阻塞线程。
2.自旋锁认为锁不会被长时间持有,使用CPU时间来换取线程上下文切换的开销,从而提高性能。但是可能会浪费CPU资源。
3.-XX:PreBlockSpin=n可以设置自旋次数(已经成为了历史),在Jdk7u40时被删除了,其实在jkd6的时候就已经无效了,现在HotSpotVM采用的是adaptive spinning(自适应自旋),虚拟机会根据情况来对每个线程使用不同的自旋次数。

CAS代码中用到的死循环获取某些东西就是自旋行为

高并发随机数ThreadLocalRandom与Random分析

Random存在性能缺陷,主要原因是要不断的计算新的种子更新原种子,使用CAS方法。高并发的情况下会造成大量的线程自旋,而只有一个线程会更新成功。
ThreadLocalRandom采用ThreadLocal的机制,每一个线程都是用自己的种子去进行计算下一个种子,规避CAS在并发下的问题。

查看Random代码

public int nextInt(int bound) {
    if (bound <= 0)
        throw new IllegalArgumentException(BadBound);

    int r = next(31);
    int m = bound - 1;
    if ((bound & m) == 0)  // i.e., bound is a power of 2
        r = (int)((bound * (long)r) >> 31);
    else {
        for (int u = r;
             u - (r = u % bound) + m < 0;
             u = next(31))
            ;
    }
    return r;
}

查看next方法

/**
 * Generates the next pseudorandom number. Subclasses should
 * override this, as this is used by all other methods.
 *
 * <p>The general contract of {@code next} is that it returns an
 * {@code int} value and if the argument {@code bits} is between
 * {@code 1} and {@code 32} (inclusive), then that many low-order
 * bits of the returned value will be (approximately) independently
 * chosen bit values, each of which is (approximately) equally
 * likely to be {@code 0} or {@code 1}. The method {@code next} is
 * implemented by class {@code Random} by atomically updating the seed to
 *  <pre>{@code (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1)}</pre>
 * and returning
 *  <pre>{@code (int)(seed >>> (48 - bits))}.</pre>
 *
 * This is a linear congruential pseudorandom number generator, as
 * defined by D. H. Lehmer and described by Donald E. Knuth in
 * <i>The Art of Computer Programming,</i> Volume 3:
 * <i>Seminumerical Algorithms</i>, section 3.2.1.
 *
 * @param  bits random bits
 * @return the next pseudorandom value from this random number
 *         generator's sequence
 * @since  1.1
 */
protected int next(int bits) {
    long oldseed, nextseed;
    AtomicLong seed = this.seed;
    do {
        //获取种子
        oldseed = seed.get();
        //计算新种子
        nextseed = (oldseed * multiplier + addend) & mask;
    } while (!seed.compareAndSet(oldseed, nextseed));
    return (int)(nextseed >>> (48 - bits));
}

通过CAS方法加循环的方式就是自旋

查看ThreadLocalRandom代码

/**
 * Returns a pseudorandom {@code int} value between zero (inclusive)
 * and the specified bound (exclusive).
 *
 * @param bound the upper bound (exclusive).  Must be positive.
 * @return a pseudorandom {@code int} value between zero
 *         (inclusive) and the bound (exclusive)
 * @throws IllegalArgumentException if {@code bound} is not positive
 */
public int nextInt(int bound) {
    if (bound <= 0)
        throw new IllegalArgumentException(BadBound);
    int r = mix32(nextSeed());
    int m = bound - 1;
    if ((bound & m) == 0) // power of two
        r &= m;
    else { // reject over-represented candidates
        for (int u = r >>> 1;
             u + m - (r = u % bound) < 0;
             u = mix32(nextSeed()) >>> 1)
            ;
    }
    return r;
}

查看nextSeed方法

final long nextSeed() {
    Thread t; long r; // read and update per-thread seed
    UNSAFE.putLong(t = Thread.currentThread(), SEED,
                   //计算新种子
                   r = UNSAFE.getLong(t, SEED) + GAMMA);
    return r;
}

查看SEED

image-20210228163402562

这三个变量在Thread类中,创建新的线程时会分配这三个变量,各自线程计算种子都使用自己的种子

image-20210228163711494

例子

random类使用

/**
 * Random类使用,性能低
 */
public class RandomDemo0 {

    public static void main(String[] args) throws InterruptedException {

        long start = System.currentTimeMillis();

        Random random = new Random();
        CountDownLatch cd = new CountDownLatch(100);
        CyclicBarrier barrier = new CyclicBarrier(100);
        ExecutorService executor = Executors.newFixedThreadPool(100);
        for(int i=0;i<100;i++){
            executor.submit(new RandomDemo0Runner(barrier,"thread"+i,random,cd));
        }

        //所有线程都执行了cd.countDown()时,继续执行
        cd.await();

        long use = System.currentTimeMillis()-start;

        System.out.println("main is over.."+use);

        executor.shutdown();
    }

}

class RandomDemo0Runner implements Runnable {

    private CyclicBarrier barrier;

    private String name;

    //多个线程使用同一个random
    private Random random;

    private CountDownLatch cd;

    public RandomDemo0Runner(CyclicBarrier barrier, String name,Random random, CountDownLatch cd) {
        super();
        this.barrier = barrier;
        this.name = name;
        this.random = random;
        this.cd = cd;
    }

    @Override
    public void run() {
        try {
            System.out.println(name + " 准备好了...");
            //所有线程都到达这里才执行
            barrier.await();
            for(int j=0;j<10000;j++) {
                //高并发CAS锁竞争
                this.random.nextInt(50);
                //System.out.println(Thread.currentThread().getName() + ">" + this.random.nextInt(50));
                //提醒: 产生大量对象
                //System.out.println(Thread.currentThread().getName() + ">" + new Random().nextInt(50));
            }
            cd.countDown();
        } catch (InterruptedException e) {
            System.out.println(name + " 中断异常!");
        } catch (BrokenBarrierException e) {
            System.out.println(name + " Barrier损坏异常!");
        }
    }
}

执行结果,使用时间90毫秒

thread98 准备好了...
thread99 准备好了...
main is over..90

ThreadLocalRandom类使用

/**
 * ThreadLocalRandom 使用,性能高
 */
public class RandomDemo1 {

    public static void main(String[] args) throws InterruptedException {

        long start = System.currentTimeMillis();
        CountDownLatch cd = new CountDownLatch(100);
        CyclicBarrier barrier = new CyclicBarrier(100);
        ExecutorService executor = Executors.newFixedThreadPool(100);
        for(int i=0;i<100;i++){
            executor.submit(new RandomDemo1Runner(barrier,"thread"+i,cd));
        }
        cd.await();
        long use = System.currentTimeMillis()-start;
        System.out.println("main is over.."+use);
        executor.shutdown();
    }
}

class RandomDemo1Runner implements Runnable {

    private CyclicBarrier barrier;

    private String name;

    private CountDownLatch cd;

    public RandomDemo1Runner(CyclicBarrier barrier, String name,CountDownLatch cd) {
        super();
        this.barrier = barrier;
        this.name = name;
        this.cd = cd;
    }

    @Override
    public void run() {
        try {
            System.out.println(name + " 准备好了...");
            barrier.await();
            for(int j=0;j<10000;j++) {
                ThreadLocalRandom.current().nextInt(50);
                //System.out.println(Thread.currentThread().getName() + ">>" + ThreadLocalRandom.current().nextInt(50));
            }
            cd.countDown();
        } catch (InterruptedException e) {
            System.out.println(name + " 中断异常!");
        } catch (BrokenBarrierException e) {
            System.out.println(name + " Barrier损坏异常!");
        }
    }
}

执行结果

thread97 准备好了...
thread98 准备好了...
thread99 准备好了...
main is over..56

查看current方法

/**
 * Returns the current thread's {@code ThreadLocalRandom}.
 *
 * @return the current thread's {@code ThreadLocalRandom}
 */
public static ThreadLocalRandom current() {
    if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
        localInit();
    return instance;
}

查看localInit方法,发现没有初始化就会进行初始化变量

/**
 * Initialize Thread fields for the current thread.  Called only
 * when Thread.threadLocalRandomProbe is zero, indicating that a
 * thread local seed value needs to be generated. Note that even
 * though the initialization is purely thread-local, we need to
 * rely on (static) atomic generators to initialize the values.
 */
static final void localInit() {
    int p = probeGenerator.addAndGet(PROBE_INCREMENT);
    int probe = (p == 0) ? 1 : p; // skip 0
    long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
    Thread t = Thread.currentThread();
    UNSAFE.putLong(t, SEED, seed);
    UNSAFE.putInt(t, PROBE, probe);
}

image-20210228170440053

高性能累加器

image-20210228203857083

LongAdder、DoubleAdder、
LongAccumulatax、DoubleAccumulator
AtomicLong存在性能瓶颈,由于使用CAS方法。高并发的情况下会造成大量的线程自旋,而只有一个线程会更新成功,浪费CPU资源。LongAdder的思想是将单一的原子变量拆分为多个变量,从而分治再合并的思想)

查看Actomic源码

使用了CAS方法,更新value属性

/**
 * Atomically increments by one the current value.
 *
 * @return the updated value
 */
public final long incrementAndGet() {
    return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}

查看getAndAddLong,有自旋行为,直到更新才停止

public final long getAndAddLong(Object var1, long var2, long var4) {
    long var6;
    do {
        var6 = this.getLongVolatile(var1, var2);
    } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));

    return var6;
}

valueOffset就是value

static {
    try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicLong.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}

private volatile long value;

LongAdder解决了高并发下自旋的问题

先进行判断并发压力,不大的情况下使用base就够了;线程增加争抢严重的时候,每个线程都去操作单独的变量cell;最后想要获取累加的结果时将所有的cell加上base得到。实际代码中cell是数组

image-20210228172302504

查看LongAdder源码(理解原理)

/**
 * Equivalent to {@code add(1)}.
 */
public void increment() {
    add(1L);
}
/**
 * Adds the given value.
 *
 * @param x the value to add
 */
public void add(long x) {
    Cell[] as; long b, v; int m; Cell a;
    //加1(x是1),如果没有设置成功,就要判断是否繁忙
    if ((as = cells) != null || !casBase(b = base, b + x)) {
        //是否是繁忙场景
        boolean uncontended = true;
        if (as == null || (m = as.length - 1) < 0 ||
            (a = as[getProbe() & m]) == null ||
            !(uncontended = a.cas(v = a.value, v + x)))
            longAccumulate(x, null, uncontended);
    }
}

查看longAccumulate方法

final void longAccumulate(long x, LongBinaryOperator fn,
                          boolean wasUncontended) {
    int h;
    if ((h = getProbe()) == 0) {
        ThreadLocalRandom.current(); // force initialization
        h = getProbe();
        wasUncontended = true;
    }
    boolean collide = false;                // True if last slot nonempty
    for (;;) {
        Cell[] as; Cell a; int n; long v;
        if ((as = cells) != null && (n = as.length) > 0) {
            if ((a = as[(n - 1) & h]) == null) {
                if (cellsBusy == 0) {       // Try to attach new Cell
                    Cell r = new Cell(x);   // Optimistically create
                    //判断是否繁忙
                    if (cellsBusy == 0 && casCellsBusy()) {
                        boolean created = false;
                        try {               // Recheck under lock
                            Cell[] rs; int m, j;
                            if ((rs = cells) != null &&
                                (m = rs.length) > 0 &&
                                rs[j = (m - 1) & h] == null) {
                                rs[j] = r;
                                created = true;
                            }
                        } finally {
                            cellsBusy = 0;
                        }
                        if (created)
                            break;
                        continue;           // Slot is now non-empty
                    }
                }
                collide = false;
            }
            else if (!wasUncontended)       // CAS already known to fail
                wasUncontended = true;      // Continue after rehash
            else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                         fn.applyAsLong(v, x))))
                break;
            else if (n >= NCPU || cells != as)
                collide = false;            // At max size or stale
            else if (!collide)
                collide = true;
            else if (cellsBusy == 0 && casCellsBusy()) {
                try {
                    if (cells == as) {      // Expand table unless stale
                        Cell[] rs = new Cell[n << 1];
                        for (int i = 0; i < n; ++i)
                            //某一个cell值计算
                            rs[i] = as[i];
                        cells = rs;
                    }
                } finally {
                    cellsBusy = 0;
                }
                collide = false;
                continue;                   // Retry with expanded table
            }
            h = advanceProbe(h);
        }
        else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
            boolean init = false;
            try {                           // Initialize table
                if (cells == as) {
                    Cell[] rs = new Cell[2];
                    rs[h & 1] = new Cell(x);
                    cells = rs;
                    init = true;
                }
            } finally {
                cellsBusy = 0;
            }
            if (init)
                break;
        }
        else if (casBase(v = base, ((fn == null) ? v + x :
                                    fn.applyAsLong(v, x))))
            break;                          // Fall back on using base
    }
}

查看LongAdder的sum方法

/**
 * Returns the current sum.  The returned value is <em>NOT</em> an
 * atomic snapshot; invocation in the absence of concurrent
 * updates returns an accurate result, but concurrent updates that
 * occur while the sum is being calculated might not be
 * incorporated.
 *
 * @return the sum
 */
//将base和cell数组中的值相加求和
public long sum() {
    Cell[] as = cells; Cell a;
    long sum = base;
    if (as != null) {
        for (int i = 0; i < as.length; ++i) {
            if ((a = as[i]) != null)
                sum += a.value;
        }
    }
    return sum;
}

查看cell源码(父类Striped64中),cell中value的更新是采用CAS的方式进行更新

/**
 * Padded variant of AtomicLong supporting only raw accesses plus CAS.
 *
 * JVM intrinsics note: It would be possible to use a release-only
 * form of CAS here, if it were provided.
 */
@sun.misc.Contended static final class Cell {
    volatile long value;
    Cell(long x) { value = x; }
    final boolean cas(long cmp, long val) {
        return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
    }

    // Unsafe mechanics
    private static final sun.misc.Unsafe UNSAFE;
    private static final long valueOffset;
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> ak = Cell.class;
            valueOffset = UNSAFE.objectFieldOffset
                (ak.getDeclaredField("value"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
}

查看Striped64的变量,通过对比CPU数和线程数判断是否繁忙

/** Number of CPUS, to place bound on table size */
static final int NCPU = Runtime.getRuntime().availableProcessors();

/**
 * Table of cells. When non-null, size is a power of 2.
 */
transient volatile Cell[] cells;

/**
 * Base value, used mainly when there is no contention, but also as
 * a fallback during table initialization races. Updated via CAS.
 */
transient volatile long base;

/**
 * Spinlock (locked via CAS) used when resizing and/or creating Cells.
 */
transient volatile int cellsBusy;

例子

做性能对比

使用原子类

package com.mkevin.demo5;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;

public class LongAdderDemo0 {

    public static void main(String[] args) throws InterruptedException {
        CyclicBarrier barrier = new CyclicBarrier(100);
        AtomicLong al = new AtomicLong();
        List<Thread> list = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        barrier.await();
                        for(int i=0;i<10000;i++) {
                            //做累加
                            al.incrementAndGet();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }
            });
            list.add(t);
        }

        long start = System.currentTimeMillis();
        for (Thread th : list) {
            th.start();
        }
        for (Thread th : list) {
            //确保100个线程都执行完毕之后再向下执行
            th.join();
        }
        long end = System.currentTimeMillis();
        System.out.println("耗时:" + (end - start) + "ms, sum:" + al.get());
    }

}

运行结果

耗时:32ms, sum:1000000

使用LongAdder

package com.mkevin.demo5;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;

/**
 * LongAddr 和 DoubleAddr的性能极高,采用cell的方式进行分化处理,比Atomic的性能更强
 * <p>
 * https://blog.csdn.net/fouy_yun/article/details/77825039
 */
public class LongAdderDemo1 {

    public static void main(String[] args) throws InterruptedException {
        CyclicBarrier barrier = new CyclicBarrier(100);
        LongAdder longAdder = new LongAdder();
        List<Thread> list = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        barrier.await();
                        for(int i=0;i<10000;i++) {
                            longAdder.increment();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }
            });
            list.add(t);
        }

        long start = System.currentTimeMillis();
        for (Thread th : list) {
            th.start();
        }
        for (Thread th : list) {
            th.join();
        }
        long end = System.currentTimeMillis();

        System.out.println("耗时:" + (end - start) + "ms, sum:" + longAdder.sum());
    }

}

运行结果

耗时:27ms, sum:1000000

Striped64是数字,继承自Number

累加器家族

进行多个操作的话要自行使用锁保证原子性,因为没有提供复杂的操作

没有incrementAndGet、decrementAndGet这种方法,只有单独的increment、longValue这种方法,如果组合使用则需要自己做同步控制,否则无法保证原子性。
LongAddr本质上是一种空间换时间的策略,累加器家族还有以下3种
java.util.concurrent.atomic.DoubleAdder
java.util.concurrent.atomic.LongAccumulator
java.util.concurrent.atomic.DoubleAccumulator(可自定义累加算法)
LongAdder是LongAccumulator的特例,DoubleAdder是DoubleAccumulator的特列,Accumulator的特点是可以设置初始值、自定义累加算法

LongAccumulator源码如下

public class LongAccumulator extends Striped64 implements Serializable {
    private static final long serialVersionUID = 7249069246863182397L;

    //自定义计算的接口
    private final LongBinaryOperator function;
    private final long identity;

    /**
     * Creates a new instance using the given accumulator function
     * and identity element.
     * @param accumulatorFunction a side-effect-free function of two arguments
     * @param identity identity (initial value) for the accumulator function
     */
    public LongAccumulator(LongBinaryOperator accumulatorFunction,
                           long identity) {
        this.function = accumulatorFunction;
        base = this.identity = identity;
    }

    /**
     * Updates with the given value.
     *
     * @param x the value
     */
    //执行计算
    public void accumulate(long x) {
        Cell[] as; long b, v, r; int m; Cell a;
        if ((as = cells) != null ||
            (r = function.applyAsLong(b = base, x)) != b && !casBase(b, r)) {
            boolean uncontended = true;
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[getProbe() & m]) == null ||
                !(uncontended =
                  (r = function.applyAsLong(v = a.value, x)) == v ||
                  a.cas(v, r)))
                longAccumulate(x, function, uncontended);
        }
    }
    //省略...
}

查看LongBinaryOperator源码

/**
 * Represents an operation upon two {@code long}-valued operands and producing a
 * {@code long}-valued result.   This is the primitive type specialization of
 * {@link BinaryOperator} for {@code long}.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #applyAsLong(long, long)}.
 *
 * @see BinaryOperator
 * @see LongUnaryOperator
 * @since 1.8
 */
@FunctionalInterface
public interface LongBinaryOperator {

    /**
     * Applies this operator to the given operands.
     *
     * @param left the first operand
     * @param right the second operand
     * @return the operator result
     */
    long applyAsLong(long left, long right);
}

例子

package com.mkevin.demo5;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.function.LongBinaryOperator;

/**
 * LongAccumulator是LongAdder的功能增强版。LongAdder的API只有对数值的加减,而LongAccumulator提供了自定义的函数操作
 *
 * https://blog.csdn.net/fouy_yun/article/details/77825039
 */
public class LongAccumulatorDemo1 {

    public static void main(String[] args) throws InterruptedException {

        long start = System.currentTimeMillis();

        LongBinaryOperator lbn = new LongBinaryOperator() {
            @Override
            public long applyAsLong(long left, long right) {
                return left+right*2+ ThreadLocalRandom.current().nextInt(10);
            }
        };

        LongAccumulator la = new LongAccumulator(lbn,1);
        System.out.println("初始值:"+la.longValue());

        List<Thread> list = new ArrayList<>();
        for(int i=0;i<1000;i++){
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    la.accumulate(2);
                }
            });
            list.add(t);
        }

        for(Thread th : list){
            th.start();
        }

        for(Thread th : list){
            th.join();
        }

        long end = System.currentTimeMillis();

        System.out.println("耗时:"+(end-start)+",结果:"+la.longValue());

    }

}

运行结果

初始值:1
耗时:89,结果:16570
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值