并发发展
1.synchronized
1.拿到锁的线程一直不释放锁如何处理
2.大量资源竞争,消耗CPU,同时会造成死锁或其他安全问题
3.每次执行synchronized需上下文切换,消耗特大
4.大多数场景,并发操作粒度很小,synchronized有sjynd嫌疑
2.volatile
- 解决synchronized问题
- 非阻塞volatile解决共享变量在多线程间的可见性
- 但volatile不保证原子性
3.CAS
- JDK提供的非阻塞原子性操作,通过硬件保证比较-更新的操作原子性
CAS
1.概述
- Compare And Swap
- 线程安全的操作类
- 无锁算法
- 乐观锁的实现
悲观锁(独占锁synchronized):假设最坏的情况,只有在确保其它线程不会造成干扰的情况下执行,会导致其它所有需要锁的线程挂起直到持有锁的线程释放锁
乐观锁:每次不加锁,假设没有冲突而去完成某项操作,如果发生冲突就重试,直到成功为止
2.CAS原理
- 3个操作数:内存所存值V,原值A,要改的值B
- 当且仅当V=A时,将V改为B并返回true,否则返回false
- 若V和A一直不相等,则一直循环该CAS操作(自旋do-while)
3.CAS问题
- ABA问题
- CPU开销大(自旋)
- 一次能保证一个共享变量的原子操作
Unsafe
1.概述
- 提供类似C++的手动内存管理能力
- 全限定名是sun.misc.Unsafe
- final修饰类,不允许继承
- private修饰构造方法,不允许实例化
2.作用
3.创建
private static final Unsafe unsafe = Unsafe.getUnsafe();
当使用getUnsafe获取时必须保证是根加载器BootStrap,否则抛出异常(java.lang.SecurityException: Unsafe)
-->此处是Launcher$Application加载器
---------------------------------------
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
---------------------------------------
public static boolean isSystemDomainLoader(ClassLoader var0) {
return var0 == null;
}
static long offset;
private volatile int state = 0;
static Unsafe unsafe;
static {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
offset = unsafe.objectFieldOffset(unsafe_error.class.getDeclaredField("state"));
} catch (Exception e) {
e.printStackTrace();
}
}
4.方法
Atomic
1.原子基础数据类(AtomicInteger)
AtomicInteger atomicInteger = new AtomicInteger(10);
System.out.println(atomicInteger.getAndIncrement());
System.out.println(atomicInteger.incrementAndGet());
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.addAndGet(2));
System.out.println(atomicInteger.getAndSet(2));
System.out.println(atomicInteger.get());
public final long incrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}
--------------------------------------
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;
}
2.原子引用类
- 将对整个对象的操作看成原子
- 关键加入泛型
- AtomicReference
UserInfo userInfo = new UserInfo("Mark", 15);
userRef.set(userInfo);
UserInfo updateUser = new UserInfo("Bill", 20);
userRef.compareAndSet(userInfo, updateUser);
static AtomicStampedReference<String> asr = new AtomicStampedReference<>("atomic", 0);
asr.compareAndSet(oldReference, oldReference + "JAVA", oldStamp, oldStamp + 1));
3.原子数组(AtomicIntegerArray)
static int[] value = new int[]{1, 2};
static AtomicIntegerArray ai = new AtomicIntegerArray(value);
ai.getAndSet(0, 3);
4.LongAdder
高并发下N多线程同时去操作一个变量会造成大量线程CAS失败,然后处于自旋状态,导致严重浪费CPU资源,降低并发性
既然AtomicLong性能问题是由于过多线程同时去竞争同一个变量的更新而降低的
那么如果把一个变量分解为多个变量,让同样多的线程去竞争多个资源就可以解决该性能问题
1.AtomicLong是基于CAS方式自旋更新的
包含有原子性的读写结合的API
2.LongAdder是把value分成若干cell
并发量低时,直接CAS更新值,成功即结束
并发量高时,CAS更新某个cell值和需要时对cell数据扩容,成功结束;更新失败自旋CAS更新 cell值
取值时调用sum()方法进行每个cell累加
没有原子性的读写结合的API,但能保证结果最终一致性
3.低并发场景AtomicLong和LongAdder性能相似,高并发场景LongAdder性能优于AtomicLong
- 伪共享
CPU缓存系统以缓存行(cache line)为存储单位,目前主流的CPU Cache的Cache Line大小都是64Bytes
在多线程情况下,修改共享同一缓存行的变量会无意中影响彼此的性能
多个地址连续的变量才有可能被放到同一个缓存行中
读取数组第一个元素时,多个元素会被放到同一个缓存行
顺序访问数组元素时会在缓存行中直接命中,就不会再到主内存中读取
1.填充法:为保证以下对象数据被存到同一个缓存行,如缓存行为64字节的话,基本值value占8字节,FillLong类对象的字节码的对象头占8字节,共计16字节
64-16=48字节,所以还需要补6个long型数据进去
-----------------------------------------------
static class FillLong {
public volatile long value = 0L;
public long p1, p2, p3, p4, p5, p6;
}
2.@Contended注解,默认情况下, @Contended注解只用于java核心类,如rt.jar包下的类
如用户类路径下的类要用此注解,需配置jvm参数: --XX:-RestrictContended
填充宽度为128字节,要调整的话,需设置: -XX:ContendedPaddingWidth,设置@Contended注解填充变量的宽度
此注解也可以用来修饰变量,如在LongAdder中的Cell类 @sun.misc.Contended static final class Cell
----------------------------------------------
@sun.misc.Contended
static class FillLong2 {
public volatile long value = 0L;
}