• JDK8新增


    LongAdder

    提供了原子累计值的方法。

    在高并发下N多线程同时去操作一个变量会造成大量线程CAS失败然后处于自旋状态,这大大浪费了cpu资源,降低了并发性。那么既然AtomicLong性能由于过多线程同时去竞争一个变量的更新而降低的,LongAdder思路把一个变量分解为多个变量,让同样多的线程去竞争多个资源那么性能问题得到解决

    LongAdder extends Striped64 implements Serializable

    increment() / decrement()

     1     public void increment() {
     2         add(1L);
     3     }
     4 
     5     /**
     6      * Equivalent to {@code add(-1)}.
     7      */
     8     public void decrement() {
     9         add(-1L);
    10     }

    add()

     1     public void add(long x) {
     2         Cell[] as; long b, v; int m; Cell a;
     3         if ((as = cells) != null || !casBase(b = base, b + x)) {
     4             boolean uncontended = true;
     5             if (as == null || (m = as.length - 1) < 0 ||
     6                 (a = as[getProbe() & m]) == null ||
     7                 !(uncontended = a.cas(v = a.value, v + x)))
     8                 longAccumulate(x, null, uncontended);
     9         }
    10     }

    LongAdder维护了一个延迟初始化的原子性更新数组和一个基值变量base.数组的大小保持是2的N次方大小,数组表的下标使用每个线程的hashcode值的掩码表示,数组里面的变量实体是Cell类型,Cell类型是AtomicLong的一个改进,用来减少缓存的争用,对于大多数原子操作字节填充是浪费的,因为原子性操作都是无规律的分散在内存中进行的,多个原子性操作彼此之间是没有接触的,但是原子性数组元素彼此相邻存放将能经常共享缓存行,所以这在性能上是一个提升。

    另外由于Cells占用内存是相对比较大的,所以一开始并不创建,而是在需要时候在创建,也就是惰性加载,当一开始没有空间时候,所有的更新都是操作base变量,

    StampedLock

    ReadWriteLock 写锁是互斥的

    读-写

    写-写

    ReentrantReadWriteLock是读写锁,在多线程环境下,大多数情况是读的情况远远大于写的操作,因此可能导致写的饥饿问题

    StampedLock

    读锁并不会阻塞写锁,读取失败后重新读

    writeLock()

    写锁writeLock是一个独占锁,同时只有一个线程可以获取该锁,当一个线程获取该锁后,其他请求读锁和写锁的线程必须等待,这跟ReentrantReadWriteLock 的写锁很相似,不过要注意的是StampedLock的写锁是不可重入锁,

    当目前没有线程持有读锁或者写锁的时候才可以获取到该锁,请求该锁成功后会返回一个stamp 票据变量来表示该锁的版本

    1     public long writeLock() {
    2         long s, next;  // bypass acquireWrite in fully unlocked case only
    3         return ((((s = state) & ABITS) == 0L &&
    4                  U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) ?
    5                 next : acquireWrite(false, 0L));
    6     }

    readLock()

    是个共享锁,在没有线程获取独占写锁的情况下,同时多个线程可以获取该锁;如果已经有线程持有写锁,其他线程请求获取该锁会被阻塞,这类似ReentrantReadWriteLock 的读锁(不同在于这里的读锁是不可重入锁)。

    这里说的悲观是指在具体操作数据前,悲观的认为其他线程可能要对自己操作的数据进行修改,所以需要先对数据加锁,这是在读少写多的情况下的一种考虑,请求该锁成功后会返回一个stamp票据变量来表示该锁的版本

        / * Non-exclusively acquires the lock, blocking if necessary
         * until available.
         *
         * @return a stamp that can be used to unlock or convert mode
         */
        public long readLock() {
            long s = state, next;  // bypass acquireRead on common uncontended case
            return ((whead == wtail && (s & ABITS) < RFULL &&
                     U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) ?
                    next : acquireRead(false, 0L));
        }

     乐观锁失败后锁升级为readLock():尝试state+1,用于统计读线程的数量,如果失败,进入acquireRead()进行自旋,通过CAS获取锁

    举例

     1 public class Demo {
     2     
     3     private int balance;
     4     
     5     private StampedLock lock = new StampedLock();
     6     
     7     public void conditionReadWrite (int value) {
     8         
     9         // 首先判断balance的值是否符合更新的条件
    10         long stamp = lock.readLock();
    11         while (balance > 0) {
    12             long writeStamp = lock.tryConvertToWriteLock(stamp);
    13             if(writeStamp != 0) { // 成功转换成为写锁
    14                 stamp = writeStamp;
    15                 balance += value;
    16                 break;
    17             } else {
    18                 // 没有转换成写锁,这里需要首先释放读锁,然后再拿到写锁
    19                 lock.unlockRead(stamp);
    20                 // 获取写锁
    21                 stamp = lock.writeLock();
    22             }
    23          }
    24         
    25         lock.unlock(stamp);
    26     }
    27     
    28     public void optimisticRead() {
    29         long stamp = lock.tryOptimisticRead();
    30         int c = balance;
    31         // 这里可能会出现了写操作,因此要进行判断
    32         if(!lock.validate(stamp)) {
    33             // 要从新读取
    34             long readStamp = lock.readLock();
    35             c = balance;
    36             stamp = readStamp; //更新票据,从而释放
    37         }
    38         /// 
    39         lock.unlockRead(stamp);
    40     }
    41     
    42     public void read () {
    43         long stamp = lock.readLock();
    44         lock.tryOptimisticRead();
    45         int c = balance;
    46         // ...
    47         lock.unlockRead(stamp);
    48     }
    49     
    50     public void write(int value) {
    51         long stamp = lock.writeLock();
    52         balance += value;
    53         lock.unlockWrite(stamp);
    54     }
    55     
    56 
    57 }











  • 相关阅读:
    brctl 使用说明
    Flash文件系统介绍和平台采用squashfs+ubifs原因
    xargs
    svn的常用命令
    shell中的if用法
    shell中单双引号
    删除文件中的 ^M 字符
    博客园添加live2d看板娘
    IOS 自定义转场动画
    rails
  • 原文地址:https://www.cnblogs.com/quyangyang/p/11216428.html
Copyright © 2020-2023  润新知