原子组件的实现原理CAS
应用场景
- 可用来实现变量、状态在多线程下的原子性操作
- 可用于实现同步锁(ReentrantLock)
原子组件
- 原子组件的原子性操作是靠使用cas来自旋操作volatile变量实现的
- volatile的类型变量保证变量被修改时,其他线程都能看到最新的值
- cas则保证value的修改操作是原子性的,不会被中断
基本类型原子类
AtomicBoolean
public static void main(String[] args) throws Exception {
AtomicBoolean atomicBoolean = new AtomicBoolean(false);
引用类原子类
AtomicReference
- AtomicReference的源码如下,它内部定义了一个
volatile V value
,并借助VarHandle(具体子类是FieldInstanceReadWrite)实现原子操作,MethodHandles会帮忙计算value在类的偏移位置,最后在VarHandle调用Unsafe.public final native boolean compareAndSetReference(Object o, long offset, Object expected, Object x)
方法原子修改对象的属性
public class AtomicReference<V> implements java.io.Serializable {
private static final long serialVersionUID = -1848883965231344442L;
private static final VarHandle VALUE;
static {
try {
MethodHandles.Lookup l = MethodHandles.lookup();
VALUE = l.findVarHandle(AtomicReference.class, "value", Object.class);
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
private volatile V value;
....
ABA问题
- 线程X准备将变量的值从A改为B,然而这期间线程Y将变量的值从A改为C,然后再改为A;最后线程X检测变量值是A,并置换为B。但实际上,A已经不再是原来的A了
- 解决方法,是把变量定为唯一类型。值可以加上版本号,或者时间戳。如加上版本号,线程Y的修改变为A1->B2->A3,此时线程X再更新则可以判断出A1不等于A3
- AtomicStampedReference的实现和AtomicReference差不多,不过它原子修改的变量是
volatile Pair<V> pair;
,Pair是其内部类。AtomicStampedReference可以用来解决ABA问题
public class AtomicStampedReference<V> {
private static class Pair<T> {
final T reference;
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
private volatile Pair<V> pair;
- 如果我们不关心变量在中间过程是否被修改过,而只是关心当前变量是否还是原先的变量,则可以使用AtomicMarkableReference
- AtomicStampedReference的使用示例
public class Main {
public static void main(String[] args) throws Exception {
Test old = new Test("hello"), newTest = new Test("world");
AtomicStampedReference<Test> reference = new AtomicStampedReference<>(old, 1);
reference.compareAndSet(old, newTest,1,2);
System.out.println("对象:"+reference.getReference().name+";版本号:"+reference.getStamp());
}
}
class Test{
Test(String name){ this.name = name; }
public String name;
}
---------------输出结果------------------
对象:world;版本号:2
数组原子类
AtomicIntegerArray
- 数组原子类内部会初始一个final的数组,它把整个数组当做一个对象,然后根据下标index计算法元素偏移量,再调用UNSAFE.compareAndSetReference进行原子操作。数组并没被volatile修饰,为了保证元素类型在不同线程的可见,获取元素使用到了UNSAFE
public native Object getReferenceVolatile(Object o, long offset)
方法来获取实时的元素值
- 使用示例
属性原子更新类
AtomicIntegerFieldUpdater
AtomicLongFieldUpdater
AtomicReferenceFieldUpdater
- 如果操作对象是某一类型的属性,可以使用AtomicIntegerFieldUpdater原子更新,不过类的属性需要定义成volatile修饰的变量,保证该属性在各个线程的可见性,否则会报错
- 使用示例
public class Main {
public static void main(String[] args) {
AtomicReferenceFieldUpdater<Test,String> fieldUpdater = AtomicReferenceFieldUpdater.newUpdater(Test.class,String.class,"name");
Test test = new Test("hello world");
fieldUpdater.compareAndSet(test,"hello world","siting");
System.out.println(fieldUpdater.get(test));
System.out.println(test.name);
}
}
class Test{
Test(String name){ this.name = name; }
public volatile String name;
}
---------------输出结果------------------
siting
siting
累加器
Striped64
LongAccumulator
LongAdder
- LongAccumulator和LongAdder都继承于Striped64,Striped64的主要思想是和ConcurrentHashMap有点类似,分段计算,单个变量计算并发性能慢时,我们可以把数学运算分散在多个变量,而需要计算总值时,再一一累加起来
- LongAdder相当于LongAccumulator一个特例实现
- LongAccumulator的示例
public static void main(String[] args) throws Exception {
LongAccumulator accumulator = new LongAccumulator(Long::sum, 0);
for(int i=0;i<100000;i++){
CompletableFuture.runAsync(() -> accumulator.accumulate(1));
}
Thread.sleep(1000);
同步组件的实现原理
同步组件
ReentrantLock、ReentrantReadWriteLock
- ReentrantLock、ReentrantReadWriteLock都是基于AQS(AbstractQueuedSynchronizer)实现的。因为它们有公平锁和非公平锁的区分,因此没直接继承AQS,而是使用内部类去继承,公平锁和非公平锁各自实现AQS,ReentrantLock、ReentrantReadWriteLock再借助内部类来实现同步
- ReentrantLock的使用示例
ReentrantLock lock = new ReentrantLock();
if(lock.tryLock()){
- ReentrantReadWriteLock的使用示例
public static void main(String[] args) throws Exception {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
if(lock.readLock().tryLock()){
Semaphore实现原理和使用场景
- Semaphore和ReentrantLock一样,也有公平和非公平竞争锁的策略,一样也是通过内部类继承AQS来实现同步
- 通俗解释:假设有一口井,最多有三个人的位置打水。每有一个人打水,则需要占用一个位置。当三个位置全部占满时,第四个人需要打水,则要等待前三个人中一个离开打水位,才能继续获取打水的位置
- 使用示例
public static void main(String[] args) throws Exception {
Semaphore semaphore = new Semaphore(2);
for (int i = 0; i < 3; i++)
CompletableFuture.runAsync(() -> {
try {
System.out.println(Thread.currentThread().toString() + " start ");
if(semaphore.tryAcquire(1)){
Thread.sleep(1000);
semaphore.release(1);
System.out.println(Thread.currentThread().toString() + " 无阻塞结束 ");
}else {
System.out.println(Thread.currentThread().toString() + " 被阻塞结束 ");
}
} catch (Exception e) {
throw new RuntimeException(e);
}
});
- 可以看出三个线程,因为信号量设定为2,第三个线程是无法获取信息成功的,会打印阻塞结束
CountDownLatch实现原理和使用场景
- CountDownLatch也是靠AQS实现的同步操作
- 通俗解释:玩游戏时,假如主线任务需要靠完成五个小任务,主线任务才能继续进行时。此时可以用CountDownLatch,主线任务阻塞等待,每完成一小任务,就done一次计数,直到五个小任务全部被执行才能触发主线
- 使用示例
public static void main(String[] args) throws Exception {
CountDownLatch count = new CountDownLatch(2);
for (int i = 0; i < 2; i++)
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(1000);
System.out.println(" CompletableFuture over ");
count.countDown();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
CyclicBarrier实现原理和使用场景
- CyclicBarrier则是靠
ReentrantLock lock
和Condition trip
属性来实现同步
- 通俗解释:CyclicBarrier需要阻塞全部线程到await状态,然后全部线程再全部被唤醒执行。想象有一个栏杆拦住五只羊,需要当五只羊一起站在栏杆时,栏杆才会被拉起,此时所有的羊都可以飞跑出羊圈
- 使用示例
public static void main(String[] args) throws Exception {
CyclicBarrier barrier = new CyclicBarrier(2);
CompletableFuture.runAsync(()->{
try {
System.out.println("CompletableFuture run start-"+ Clock.systemUTC().millis());
barrier.await();
StampedLock
- StampedLock不是借助AQS,而是自己内部维护多个状态值,并配合cas实现的
- StampedLock具有三种模式:写模式、读模式、乐观读模式
- StampedLock的读写锁可以相互转换
public static void main(String[] args) throws Exception {
StampedLock stampedLock = new StampedLock();
long stamp = stampedLock.tryOptimisticRead();