多线程实际开发深入学习--原子变量类
1.参考:https://www.cnblogs.com/jingmoxukong/p/12109049.html,https://blog.csdn.net/zy_remarkable/article/details/80616037
2.介绍:
(1)保证线程安全是 Java 并发编程必须要解决的重要问题。Java 从原子性、可见性、有序性这三大特性入手,确保多线程的数据一致性。
(2)确保线程安全最常见的做法是利用锁机制(Lock
、sychronized
)来对共享数据做互斥同步,这样在同一个时刻,只有一个线程可以执行某个方法或者某个代码块,那么操作必然是原子性的,线程安全的。
互斥同步最主要的问题是线程阻塞和唤醒所带来的性能问题。
(3)volatile
是轻量级的锁(自然比普通锁性能要好),它保证了共享变量在多线程中的可见性,但无法保证原子性。所以,它只能在一些特定场景下使用。
(4)为了兼顾原子性以及锁带来的性能问题,Java 引入了 CAS (主要体现在 Unsafe
类)来实现非阻塞同步(也叫乐观锁)。并基于 CAS ,提供了一套原子工具类。
3.优点:
(1) 原子变量类 比锁的粒度更细,更轻量级,并且对于在多处理器系统上实现高性能的并发代码来说是非常关键的。原子变量将发生竞争的范围缩小到单个变量上。
(2) 原子变量类相当于一种泛化的 volatile
变量,能够支持原子的、有条件的读/改/写操作。
(3) 原子类在内部使用 CAS 指令(基于硬件的支持)来实现同步。这些指令通常比锁更快。
4.组成部分(类型):
基本类型:
AtomicBoolean --- 布尔类型原子类
AtomicInteger --整型原子类
AtomicLong --长整型原子类
引用类型:
AtomicReference --引用类型原子类
AtomicMarkableReference --带有标记位的引用类型原子类
AtomicStampedReference --带有版本号的引用类型原子类
数组类型:
AtomicIntegerArray --整形数组原子类
AtomicLongArray --长整型数组原子类
AtomicReferenceArray --引用类型数组原子类
属性更新器类型:
AtomicIntegerFieldUpdater --整型字段的原子更新器。
AtomicLongFieldUpdater --长整型字段的原子更新器。
AtomicReferenceFieldUpdater --原子更新引用类型里的字段。
5.基本类型用法介绍:
(1)这一类型的原子类是针对 Java 基本类型进行操作。
(2)基本类说明:虽然 Java 只提供了 AtomicBoolean
、AtomicInteger
、AtomicLong
,但是可以模拟其他基本类型的原子变量。要想模拟其他基本类型的原子变量,
可以将 short
或 byte
等类型与 int
类型进行转换,以及使用 Float.floatToIntBits
、Double.doubleToLongBits
来转换浮点数。
(3)提供的对象方法:
public final int get() // 获取当前值
public final int getAndSet(int newValue) // 获取当前值,并设置新值
public final int getAndIncrement()// 获取当前值,并自增
public final int getAndDecrement() // 获取当前值,并自减
public final int getAndAdd(int delta) // 获取当前值,并加上预期值
boolean compareAndSet(int expect, int update) // 如果输入值(update)等于预期值,将该值设置为输入值
public final void lazySet(int newValue) // 最终设置为 newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
(4) 示例:
public class AtomicIntegerDemo {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(5);
AtomicInteger count = new AtomicInteger(0);
for (int i = 0; i < 1000; i++) {
executorService.submit((Runnable) () -> {
System.out.println(Thread.currentThread().getName() + " count=" + count.get());
count.incrementAndGet();
});
}
executorService.shutdown();
executorService.awaitTermination(30, TimeUnit.SECONDS);
System.out.println("Final Count is : " + count.get());
}
}
(5)源码实现:
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
说明:
value
- value 属性使用 volatile
修饰,使得对 value 的修改在并发环境下对所有线程可见。
valueOffset
- value 属性的偏移量,通过这个偏移量可以快速定位到 value 字段,这个是实现 AtomicInteger 的关键。
unsafe
- Unsafe 类型的属性,它为 AtomicInteger 提供了 CAS 操作。
6.引用类型用法介绍:
图解:
(1)重点介绍:AtomicStampedReference
类在引用类型原子类中,彻底地解决了 ABA 问题,其它的 CAS 能力与另外两个类相近,所以最具代表性。
(2)示例:
public class AtomicReferenceDemo2 {
private static int ticket = 10;
public static void main(String[] args) {
threadSafeDemo();
}
private static void threadSafeDemo() {
SpinLock lock = new SpinLock();
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
executorService.execute(new MyThread(lock));
}
executorService.shutdown();
}
/**
* 基于 {@link AtomicReference} 实现的简单自旋锁
*/
static class SpinLock {
private AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void lock() {
Thread current = Thread.currentThread();
while (!atomicReference.compareAndSet(null, current)) {}
}
public void unlock() {
Thread current = Thread.currentThread();
atomicReference.compareAndSet(current, null);
}
}
/**
* 利用自旋锁 {@link SpinLock} 并发处理数据
*/
static class MyThread implements Runnable {
private SpinLock lock;
public MyThread(SpinLock lock) {
this.lock = lock;
}
总结:
1.原子类的实现基于 CAS 机制,而 CAS 存在 ABA 问题(不了解 ABA 问题,可以参考:Java 并发基础机制 - CAS 的问题)。正是为了解决 ABA 问题,才有了 AtomicMarkableReference
和 AtomicStampedReference
。
2.AtomicMarkableReference
使用一个布尔值作为标记,修改时在 true / false 之间切换。这种策略不能根本上解决 ABA 问题,但是可以降低 ABA 发生的几率。常用于缓存或者状态描述这样的场景。
3.AtomicStampedReference
使用一个整型值做为版本号,每次更新前先比较版本号,如果一致,才进行修改。通过这种策略,可以根本上解决 ABA 问题。
4.示例(画圈处为重点):
7.数组类型用法介绍:
优点:已经有了针对基本类型和引用类型的原子类,为什么还要提供针对数组的原子类呢?
数组类型的原子类为 数组元素 提供了 volatile
类型的访问语义,这是普通数组所不具备的特性——volatile
类型的数组仅在数组引用上具有 volatile
语义。
示例:
public class AtomicIntegerArrayDemo {
private static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
public static void main(final String[] arguments) throws InterruptedException {
System.out.println("Init Values: ");
for (int i = 0; i < atomicIntegerArray.length(); i++) {
atomicIntegerArray.set(i, i);
System.out.print(atomicIntegerArray.get(i) + " ");
}
System.out.println();
Thread t1 = new Thread(new Increment());
Thread t2 = new Thread(new Compare());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final Values: ");
for (int i = 0; i < atomicIntegerArray.length(); i++) {
System.out.print(atomicIntegerArray.get(i) + " ");
}
System.out.println();
}
static class Increment implements Runnable {