实现全局自增 id 最简单有效的方式是什么?
包定义了一些常见类型的原子变量。这些原子变量为我们提供了一种操作单一变量无锁 (lock-free) 的线程安全 (thread-safe) 方式。实际上该包下面的类为我们提供了类似
- java.util.concurrent.atomic
变量的特性,同时还提供了诸如
- volatile
的功能。不使用锁实现线程安全听起来似乎很不可思议,这其实是通过 CPU 的 compare and swap 指令实现的,由于硬件指令支持当然不需要加锁了。
- boolean compareAndSet(expectedValue, updateValue)
先不去讨论这些细节,我们来看一下原子变量的用法。一个典型的用法是可以使用原子变量轻松实现全局自增 id,就像下面这样:
- // 线程安全的序列id生成器
- classSequencer {private finalAtomicLong sequenceNumber =newAtomicLong(0);public long next() {returnsequenceNumber.getAndIncrement();
- }
- }
上述代码利用 AtomicLong 创建了一个 Sequencer 类,不断调用该类的 next() 方法就可以得到线程安全的自增 id,用起来非常简单直观。下面我们给出每种原子变量类型的用法说明。
AtomicInteger 和 AtomicLong 分别代表原子类型的整型和长整型,这两个类提供十分相似的功能,仅仅是位宽不同。如上例所示,原子整型可用于多线程下全局自增 id,除此之外还提供了原子比较 - 赋值等操作,诸如
,
- compareAndSet(expect, update)
,
- decrementAndGet()
,
- getAndDecrement()
等等,更全面的接口描述可参考 JDK 文档。需要提醒的是这些函数都是通过原子 CPU 指令实现,执行效率较高。
- getAndSet(newValue)
原子整型看似跟普通整型 (Integer, Long) 类型相似,但不能使用原子整型替代普通整型,因为原子整型是可变的,而普通整型不可变。由于这个原因,使用原子整型作为 Map 的 key 并不是个好主意。
你可能会想当然的以为应该有 AtomicFloat 和 AtomicDouble,遗憾的是类库里并没有这两个类型,AtomicByte 和 AtomicShort 也没有。如果需要替代方案是使用 AtomicInteger 和 AtomicLong。可通过
和
- Float.floatToRawIntBits(float)
将 Float 存储到 AtomicInteger 中,类似的 Double 类型也可以存储到 AtomicLong 中。
- Float.intBitsToFloat(int)
AtomicReference 用于存放一个可以原子更新的对象引用。该类包含
,
- get()
,
- set()
,
- compareAndSet()
等原子方法来获取和更新其代表的对象引用。
- getAndSet()
atomic 包下面有三种原子数组:
,
- AtomicIntegerArray
,
- AtomicLongArra
,分别代表整型、长整型和引用类型的原子数组。原子数组使得我们可以线程安全的方式去修改和访问数组里的单个元素。简单示例如下:
- AtomicReferenceArray
- // 原子数组示例AtomicLongArray longArray =newAtomicLongArray(10);// 创建长度为10的原子数组longArray.set(1,100);longv = longArray.getAndIncrement(1);
- AtomicReferenceArray referenceArray = newAtomicReferenceArray<>(16);
- referenceArray.set(3,"love");
- referenceArray.compareAndSet(3,"love","you");
简单来说原子数组就是一种支持线程安全的数组,仍然具有数组 "定长" 的性质,如果访问元素超过了数组的长度,将会抛出
。你可能已经想到了,可以使用线程安全的容器来避免容量不足,我们会在后续章节介绍。
- IndexOutOfBoundsException
线程安全是指多线程访问是时,无论线程的调度策略是什么,程序能够正确的执行。导致线程不安全的一个原因是状态不一致,如果线程 A 修改了某个共享变量(比如给 id++),而线程 B 没有及时知道,就会导致 B 在错误的状态上执行,结果的正确性也就无法保证。原子变量为我们提供了一种保证单个状态一致的简单方式,一个线程修改了原子变量,另外的线程立即就能看到,这比通过锁实现的方式效率要高;如果要同时保证多个变量状态一致,就只能使用锁了。
本文地址
来源: http://www.cnblogs.com/CarpenterLee/p/6695051.html