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 }