目录
资源分配(互斥)
资源分配(互斥)主要指保证同一时刻只允许一个/部分线程访问共享资源;
解决资源分配(互斥)问题的方案:
1, 管程(Synchronized,并发包中的Lock),通过加互斥锁,将共享变量及其对共享
变量的操作统一封装起来。保证同一时刻,只有一个/部分的线程能访问共享资源。
2,无锁方案;
不变模式;
写时复制模式Copy-on-Write;
线程本地存储模式ThreadLocal;
CAS和原子类;
管程内容详见: 协作篇章中管程模块;
无锁方案相对于互斥锁方案,性能更好,基本不会出现死锁问题;但是在自旋很多次时,可
能会出现饥饿和活锁问题。
不变模式
解决并发问题,最简单的办法就是让共享变量只有读操作,而没有写操作。
不变性,就是对象一旦被创建之后,状态就不再发生变化。
将一个类所有的属性都设置成 final 的,并且只允许存在只读方法,那么这个类基本上就具备
不可变性了。更严格的做法是这个类本身也是 final 的,也就是不允许继承。
String 和 Long、Integer、Double 等基础类型的包装类都具备不可变性,这些对象的线程安
全性都是靠不可变性来保证的。
不可变性的类,需要提供类似修改的功能时:创建一个新的不可变对象。
享元模式
不可变性的类,修改的是创建一个新的不可变对象,享元模式可以避免创建重复对象。
享元模式本质上其实就是一个对象池,利用享元模式创建对象的逻辑:创建之前,首先去对
象池里看看是不是存在;如果已经存在,就利用对象池里的对象;如果不存在,就会新创建一个对
象,并且把这个新创建出来的对象放进对象池里。
Long 内部维护了一个静态的对象池,仅缓存了[-128,127]之间的数字,这个对象池在 JVM 启
动的时候就创建好了,而且这个对象池一直都不会变化。
基本上所有的基础类型的包装类都不适合做锁,因为它们内部用到了享元模式,这会导致看
上去私有的锁,其实是共有的。详见:协作篇章,用锁的注意要点模块。
写时复制模式 Copy-on-Write
原理:在有写操作时,复制共享的数据;
使用场景:改动不大,数据不多,但是访问量很大,对性能高要求很高;例如:负载均衡的
路由表。
CopyOnWriteArrayList 和 CopyOnWriteArraySet 两个 Copy-on-Write 容器;这两个容器实现
的读操作是无锁的,所以读取性能很好;
Copy-on-Write 的缺点就是消耗内存。所有数据的修改都需要复制一份。
Java 中的基本数据类型 String、Integer、Long 等都是基于 Copy-on-Write 方案实现的。
线程本地存储模式 - ThreadLocal
每个线程都拥有自己的变量,彼此之间不共享,也就没有并发问题了。
Java 语言中的ThreadLocal就是线程本地存储模式的实现;
Thread持有ThreadLocalMap,ThreadLocalMap中包含的是ThreadLocal和对应的值;
CAS和原子类
CAS是CPU指令,全称是 Compare And Swap,即“比较并交换”;CAS 指令本身是能够保证
原子性的;
CAS 指令包含 3 个参数:共享变量的内存地址 A、用于比较的值 B 和共享变量的新值 C;并
且只有当内存中地址 A 处的值等于 B 时,才能将内存中地址 A 处的值更新为新值 C。
CAS 方案中,有一个问题可能会常被你忽略,那就是 ABA 的问题。
在需要关心ABA问题的场景下,需要做进一步的检查,校验A是否发生变化;
Java并发包里提供的原子类分为五个类别:原子化的基本数据类型、原子化的对象引用类
型、原子化数组、原子化对象属性更新器和原子化的累加器。
原子化的基本数据类型
AtomicBoolean、AtomicInteger 和 AtomicLong;
具备原子化的运算和赋值和修改操作;
getAndAdd(delta)
compareAndSet(expect, update)
getAndUpdate(func)
原子化的对象引用类型
AtomicReference、AtomicStampedReference 和 AtomicMarkableReference
具备原子化的运算和赋值和修改操作;
AtomicStampedReference 和 AtomicMarkableReference 这两个原子类可以解决 ABA 问题。
原子化数组
AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray
原子化对象属性更新器
tomicIntegerFieldUpdater、AtomicLongFieldUpdater 和 AtomicReferenceFieldUpdater,利
用它们可以原子化地更新对象的属性;
被修改的对象属性必须是 volatile 类型的,只有这样才能保证可见性;如果对象属性不是
volatile 类型的,newUpdater() 方法会抛出 IllegalArgumentException 这个运行时异常。
原子化的累加器
DoubleAccumulator、DoubleAdder、LongAccumulator 和 LongAdder
执行累加操作,相比原子化的基本数据类型,速度更快,但是不支持 compareAndSet() 方法。