Atomic原子类
Atomic 原子类介绍
Atomic 翻译成中文是原子的意思。在这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
所以,所谓原子类说简单点就是具有原子/原子操作特征的类。
并发包 java.util.concurrent 的原子类都存放在java.util.concurrent.atomic下。
根据操作的数据类型,可以将 JUC 包中的原子类分为 4 类
-
基本类型
使用原子的方式更新基本类型
AtomicInteger:整型原子类AtomicLong:长整型原子类AtomicBoolean:布尔型原子类
-
数组类型
使用原子的方式更新数组里的某个元素
AtomicIntegerArray:整型数组原子类AtomicLongArray:长整型数组原子类AtomicReferenceArray:引用类型数组原子类
-
引用类型
AtomicReference:引用类型原子类AtomicMarkableReference:原子更新带有标记的引用类型。该类将 boolean 标记与引用关联起来。AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
注意:
AtomicMarkableReference不能解决 ABA 问题。 -
对象的属性修改类型
AtomicIntegerFieldUpdater:原子更新整型字段的更新器AtomicLongFieldUpdater:原子更新长整型字段的更新器AtomicReferenceFieldUpdater:原子更新引用类型里的字段
基本类型原子类
使用原子的方式更新基本类型
AtomicInteger:整型原子类AtomicLong:长整型原子类AtomicBoolean:布尔型原子类
上面三个类提供的方法几乎相同,所以我们这里以 AtomicInteger 为例子来介绍。
AtomicInteger 类常用方法
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 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。AtomicInteger 类使用示例 :
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerTest {
public static void main(String[] args) { int temvalue = 0; AtomicInteger i = new AtomicInteger(0); temvalue = i.getAndSet(3); System.out.println("temvalue:" + temvalue + "; i:" + i); //temvalue:0; i:3 temvalue = i.getAndIncrement(); System.out.println("temvalue:" + temvalue + "; i:" + i); //temvalue:3; i:4 temvalue = i.getAndAdd(5); System.out.println("temvalue:" + temvalue + "; i:" + i); //temvalue:4; i:9 }}基本数据类型原子类的优势
通过一个简单例子带大家看一下基本数据类型原子类的优势
-
多线程环境不使用原子类保证线程安全(基本数据类型)
class Test {private volatile int count = 0;//若要线程安全执行执行count++,需要加锁public synchronized void increment() {count++;}public int getCount() {return count;}} -
多线程环境使用原子类保证线程安全(基本数据类型)
class Test2 {private AtomicInteger count = new AtomicInteger();public void increment() {count.incrementAndGet();}//使用AtomicInteger之后,不需要加锁,也可以实现线程安全。public int getCount() {return count.get();}}
AtomicInteger 线程安全原理简单分析
AtomicInteger 类的部分源码:
// setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用) 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;AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
CAS 的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址。另外 value 是一个 volatile 变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。
数组类型原子类
使用原子的方式更新数组里的某个元素
AtomicIntegerArray:整形数组原子类AtomicLongArray:长整形数组原子类AtomicReferenceArray:引用类型数组原子类
上面三个类提供的方法几乎相同,所以我们这里以 AtomicIntegerArray 为例子来介绍。
AtomicIntegerArray 类常用方法:
public final int get(int i) //获取 index=i 位置元素的值public final int getAndSet(int i, int newValue)//返回 index=i 位置的当前的值,并将其设置为新值:newValuepublic final int getAndIncrement(int i)//获取 index=i 位置元素的值,并让该位置的元素自增public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减public final int getAndAdd(int i, int delta) //获取 index=i 位置元素的值,并加上预期的值boolean compareAndSet(int i, int expect, int update) //如果输入的数值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update)public final void lazySet(int i, int newValue)//最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。AtomicIntegerArray 类使用示例 :
import java.util.concurrent.atomic.AtomicIntegerArray;
public class AtomicIntegerArrayTest {
public static void main(String[] args) { int temvalue = 0; int[] nums = { 1, 2, 3, 4, 5, 6 }; AtomicIntegerArray i = new AtomicIntegerArray(nums); for (int j = 0; j < nums.length; j++) { System.out.println(i.get(j)); } temvalue = i.getAndSet(0, 2); System.out.println("temvalue:" + temvalue + "; i:" + i); temvalue = i.getAndIncrement(0); System.out.println("temvalue:" + temvalue + "; i:" + i); temvalue = i.getAndAdd(0, 5); System.out.println("temvalue:" + temvalue + "; i:" + i); }
}引用类型原子类
基本类型原子类只能更新一个变量,如果需要原子更新多个变量,需要使用 引用类型原子类。
AtomicReference:引用类型原子类AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。AtomicMarkableReference:原子更新带有标记的引用类型。该类将 boolean 标记与引用关联起来
使用示例
-
AtomicReference类:import java.util.concurrent.atomic.AtomicReference;public class AtomicReferenceTest {public static void main(String[] args) {AtomicReference < Person > ar = new AtomicReference < Person > ();Person person = new Person("SnailClimb", 22);ar.set(person);Person updatePerson = new Person("Daisy", 20);ar.compareAndSet(person, updatePerson);System.out.println(ar.get().getName());System.out.println(ar.get().getAge());}}class Person {private String name;private int age;public Person(String name, int age) {super();this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}}上述代码首先创建了一个
Person对象,然后把Person对象设置进AtomicReference对象中,然后调用compareAndSet方法,该方法就是通过 CAS 操作设置 ar。如果 ar 的值为person的话,则将其设置为updatePerson。实现原理与AtomicInteger类中的compareAndSet方法相同。运行上面的代码后的输出结果如下:Daisy20 -
AtomicStampedReference类使用示例 :import java.util.concurrent.atomic.AtomicStampedReference;public class AtomicStampedReferenceDemo {public static void main(String[] args) {// 实例化、取当前值和 stamp 值final Integer initialRef = 0, initialStamp = 0;final AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(initialRef, initialStamp);System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp());// compare and setfinal Integer newReference = 666, newStamp = 999;final boolean casResult = asr.compareAndSet(initialRef, newReference, initialStamp, newStamp);System.out.println("currentValue=" + asr.getReference()+ ", currentStamp=" + asr.getStamp()+ ", casResult=" + casResult);// 获取当前的值和当前的 stamp 值int[] arr = new int[1];final Integer currentValue = asr.get(arr);final int currentStamp = arr[0];System.out.println("currentValue=" + currentValue + ", currentStamp=" + currentStamp);// 单独设置 stamp 值final boolean attemptStampResult = asr.attemptStamp(newReference, 88);System.out.println("currentValue=" + asr.getReference()+ ", currentStamp=" + asr.getStamp()+ ", attemptStampResult=" + attemptStampResult);// 重新设置当前值和 stamp 值asr.set(initialRef, initialStamp);System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp());// [不推荐使用,除非搞清楚注释的意思了] weak compare and set// 困惑!weakCompareAndSet 这个方法最终还是调用 compareAndSet 方法。[版本: jdk-8u191]// 但是注释上写着 "May fail spuriously and does not provide ordering guarantees,// so is only rarely an appropriate alternative to compareAndSet."// todo 感觉有可能是 jvm 通过方法名在 native 方法里面做了转发final boolean wCasResult = asr.weakCompareAndSet(initialRef, newReference, initialStamp, newStamp);System.out.println("currentValue=" + asr.getReference()+ ", currentStamp=" + asr.getStamp()+ ", wCasResult=" + wCasResult);}}输出结果如下:
currentValue=0, currentStamp=0currentValue=666, currentStamp=999, casResult=truecurrentValue=666, currentStamp=999currentValue=666, currentStamp=88, attemptStampResult=truecurrentValue=0, currentStamp=0currentValue=666, currentStamp=999, wCasResult=true -
AtomicMarkableReference类使用示例 :import java.util.concurrent.atomic.AtomicMarkableReference;public class AtomicMarkableReferenceDemo {public static void main(String[] args) {// 实例化、取当前值和 mark 值final Boolean initialRef = null, initialMark = false;final AtomicMarkableReference<Boolean> amr = new AtomicMarkableReference<>(initialRef, initialMark);System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked());// compare and setfinal Boolean newReference1 = true, newMark1 = true;final boolean casResult = amr.compareAndSet(initialRef, newReference1, initialMark, newMark1);System.out.println("currentValue=" + amr.getReference()+ ", currentMark=" + amr.isMarked()+ ", casResult=" + casResult);// 获取当前的值和当前的 mark 值boolean[] arr = new boolean[1];final Boolean currentValue = amr.get(arr);final boolean currentMark = arr[0];System.out.println("currentValue=" + currentValue + ", currentMark=" + currentMark);// 单独设置 mark 值final boolean attemptMarkResult = amr.attemptMark(newReference1, false);System.out.println("currentValue=" + amr.getReference()+ ", currentMark=" + amr.isMarked()+ ", attemptMarkResult=" + attemptMarkResult);// 重新设置当前值和 mark 值amr.set(initialRef, initialMark);System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked());// [不推荐使用,除非搞清楚注释的意思了] weak compare and set// 困惑!weakCompareAndSet 这个方法最终还是调用 compareAndSet 方法。[版本: jdk-8u191]// 但是注释上写着 "May fail spuriously and does not provide ordering guarantees,// so is only rarely an appropriate alternative to compareAndSet."// todo 感觉有可能是 jvm 通过方法名在 native 方法里面做了转发final boolean wCasResult = amr.weakCompareAndSet(initialRef, newReference1, initialMark, newMark1);System.out.println("currentValue=" + amr.getReference()+ ", currentMark=" + amr.isMarked()+ ", wCasResult=" + wCasResult);}}输出结果如下:
currentValue=null, currentMark=falsecurrentValue=true, currentMark=true, casResult=truecurrentValue=true, currentMark=truecurrentValue=true, currentMark=false, attemptMarkResult=truecurrentValue=null, currentMark=falsecurrentValue=true, currentMark=true, wCasResult=true
对象的属性修改类型原子类
如果需要原子更新某个类里的某个字段时,需要用到对象的属性修改类型原子类。
AtomicIntegerFieldUpdater:原子更新整形字段的更新器AtomicLongFieldUpdater:原子更新长整形字段的更新器AtomicReferenceFieldUpdater:原子更新引用类型里的字段的更新器
要想原子地更新对象的属性需要两步。第一步,因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法 newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。第二步,更新的对象属性必须使用 public volatile 修饰符。
上面三个类提供的方法几乎相同,所以我们这里以 AtomicIntegerFieldUpdater为例子来介绍。
AtomicIntegerFieldUpdater 类使用示例 :
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicIntegerFieldUpdaterTest { public static void main(String[] args) { AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
User user = new User("Java", 22); System.out.println(a.getAndIncrement(user));// 22 System.out.println(a.get(user));// 23 }}
class User { private String name; public volatile int age;
public User(String name, int age) { super(); this.name = name; this.age = age; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}常见并发容器
JDK 提供的这些容器大部分在 java.util.concurrent 包中。
ConcurrentHashMap: 线程安全的HashMapCopyOnWriteArrayList: 线程安全的List,在读多写少的场合性能非常好,远远好于Vector。ConcurrentLinkedQueue: 高效的并发队列,使用链表实现。可以看做一个线程安全的LinkedList,这是一个非阻塞队列。BlockingQueue: 这是一个接口,JDK 内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道。ConcurrentSkipListMap: 跳表的实现。这是一个 Map,使用跳表的数据结构进行快速查找。
ConcurrentHashMap
我们知道 HashMap 不是线程安全的,在并发场景下如果要保证一种可行的方式是使用 Collections.synchronizedMap() 方法来包装我们的 HashMap。但这是通过使用一个全局的锁来同步不同线程间的并发访问,因此会带来不可忽视的性能问题。
所以就有了 HashMap 的线程安全版本—— ConcurrentHashMap 的诞生。
在 JDK1.7 的时候,ConcurrentHashMap 对整个桶数组进行了分割分段(Segment,分段锁),每一把锁只锁容器其中一部分数据(下面有示意图),多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。
到了 JDK1.8 的时候,ConcurrentHashMap 已经摒弃了 Segment 的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6 以后 synchronized 锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在 JDK1.8 中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本。
CopyOnWriteArrayList
在 JDK1.5 之前,如果想要使用并发安全的 List 只能选择 Vector。而 Vector 是一种老旧的集合,已经被淘汰。Vector 对于增删改查等方法基本都加了 synchronized,这种方式虽然能够保证同步,但这相当于对整个 Vector 加上了一把大锁,使得每个方法执行的时候都要去获得锁,导致性能非常低下。
JDK1.5 引入了 Java.util.concurrent(JUC)包,其中提供了很多线程安全且并发性能良好的容器,其中唯一的线程安全 List 实现就是 CopyOnWriteArrayList 。
对于大部分业务场景来说,读取操作往往是远大于写入操作的。由于读取操作不会对原有数据进行修改,因此,对于每次读取都进行加锁其实是一种资源浪费。相比之下,我们应该允许多个线程同时访问 List 的内部数据,毕竟对于读取操作来说是安全的。
这种思路与 ReentrantReadWriteLock 读写锁的设计思想非常类似,即读读不互斥、读写互斥、写写互斥(只有读读不互斥)。CopyOnWriteArrayList 更进一步地实现了这一思想。为了将读操作性能发挥到极致,CopyOnWriteArrayList 中的读取操作是完全无需加锁的。更加厉害的是,写入操作也不会阻塞读取操作,只有写写才会互斥。这样一来,读操作的性能就可以大幅度提升。
CopyOnWriteArrayList 线程安全的核心在于其采用了 写时复制(Copy-On-Write) 的策略,从 CopyOnWriteArrayList 的名字就能看出了。
当需要修改( add,set、remove 等操作) CopyOnWriteArrayList 的内容时,不会直接修改原数组,而是会先创建底层数组的副本,对副本数组进行修改,修改完之后再将修改后的数组赋值回去,这样就可以保证写操作不会影响读操作了。
ConcurrentLinkedQueue
Java 提供的线程安全的 Queue 可以分为阻塞队列和非阻塞队列,其中阻塞队列的典型例子是 BlockingQueue,非阻塞队列的典型例子是 ConcurrentLinkedQueue,在实际应用中要根据实际需要选用阻塞队列或者非阻塞队列。 阻塞队列可以通过加锁来实现,非阻塞队列可以通过 CAS 操作实现。
从名字可以看出,ConcurrentLinkedQueue这个队列使用链表作为其数据结构.ConcurrentLinkedQueue 应该算是在高并发环境中性能最好的队列了。它之所有能有很好的性能,是因为其内部复杂的实现。
ConcurrentLinkedQueue 内部代码我们就不分析了,大家知道 ConcurrentLinkedQueue 主要使用 CAS 非阻塞算法来实现线程安全就好了。
ConcurrentLinkedQueue 适合在对性能要求相对较高,同时对队列的读写存在多个线程同时进行的场景,即如果对队列加锁的成本较高则适合使用无锁的 ConcurrentLinkedQueue 来替代。
BlockingQueue
BlockingQueue 简介
上面我们己经提到了 ConcurrentLinkedQueue 作为高性能的非阻塞队列。下面我们要讲到的是阻塞队列——BlockingQueue。阻塞队列(BlockingQueue)被广泛使用在“生产者-消费者”问题中,其原因是 BlockingQueue 提供了可阻塞的插入和移除的方法。当队列容器已满,生产者线程会被阻塞,直到队列未满;当队列容器为空时,消费者线程会被阻塞,直至队列非空时为止。
BlockingQueue 是一个接口,继承自 Queue,所以其实现类也可以作为 Queue 的实现来使用,而 Queue 又继承自 Collection 接口。下面是 BlockingQueue 的相关实现类:
下面主要介绍一下 3 个常见的 BlockingQueue 的实现类:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue 。
ArrayBlockingQueue
ArrayBlockingQueue 是 BlockingQueue 接口的有界队列实现类,底层采用数组来实现。
public class ArrayBlockingQueue<E>extends AbstractQueue<E>implements BlockingQueue<E>, Serializable{}ArrayBlockingQueue 一旦创建,容量不能改变。其并发控制采用可重入锁 ReentrantLock ,不管是插入操作还是读取操作,都需要获取到锁才能进行操作。当队列容量满时,尝试将元素放入队列将导致操作阻塞;尝试从一个空队列中取一个元素也会同样阻塞。
ArrayBlockingQueue 默认情况下不能保证线程访问队列的公平性,所谓公平性是指严格按照线程等待的绝对时间顺序,即最先等待的线程能够最先访问到 ArrayBlockingQueue。而非公平性则是指访问 ArrayBlockingQueue 的顺序不是遵守严格的时间顺序,有可能存在,当 ArrayBlockingQueue 可以被访问时,长时间阻塞的线程依然无法访问到 ArrayBlockingQueue。如果保证公平性,通常会降低吞吐量。如果需要获得公平性的 ArrayBlockingQueue,可采用如下代码:
private static ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(10,true);LinkedBlockingQueue
LinkedBlockingQueue 底层基于单向链表实现的阻塞队列,可以当做无界队列也可以当做有界队列来使用,同样满足 FIFO 的特性,与 ArrayBlockingQueue 相比起来具有更高的吞吐量,为了防止 LinkedBlockingQueue 容量迅速增,损耗大量内存。通常在创建 LinkedBlockingQueue 对象时,会指定其大小,如果未指定,容量等于 Integer.MAX_VALUE 。
相关构造方法:
/** *某种意义上的无界队列 * Creates a {@code LinkedBlockingQueue} with a capacity of * {@link Integer#MAX_VALUE}. */ public LinkedBlockingQueue() { this(Integer.MAX_VALUE); }
/** *有界队列 * Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity. * * @param capacity the capacity of this queue * @throws IllegalArgumentException if {@code capacity} is not greater * than zero */ public LinkedBlockingQueue(int capacity) { if (capacity <= 0) throw new IllegalArgumentException(); this.capacity = capacity; last = head = new Node<E>(null); }PriorityBlockingQueue
PriorityBlockingQueue 是一个支持优先级的无界阻塞队列。默认情况下元素采用自然顺序进行排序,也可以通过自定义类实现 compareTo() 方法来指定元素排序规则,或者初始化时通过构造器参数 Comparator 来指定排序规则。
PriorityBlockingQueue 并发控制采用的是可重入锁 ReentrantLock,队列为无界队列(ArrayBlockingQueue 是有界队列,LinkedBlockingQueue 也可以通过在构造函数中传入 capacity 指定队列最大的容量,但是 PriorityBlockingQueue 只能指定初始的队列大小,后面插入元素的时候,如果空间不够的话会自动扩容)。
简单地说,它就是 PriorityQueue 的线程安全版本。不可以插入 null 值,同时,插入队列的对象必须是可比较大小的(comparable),否则报 ClassCastException 异常。它的插入操作 put 方法不会 block,因为它是无界队列(take 方法在队列为空的时候会阻塞)。
ConcurrentSkipListMap
为了引出 ConcurrentSkipListMap,先带着大家简单理解一下跳表。
对于一个单链表,即使链表是有序的,如果我们想要在其中查找某个数据,也只能从头到尾遍历链表,这样效率自然就会很低,跳表就不一样了。跳表是一种可以用来快速查找的数据结构,有点类似于平衡树。它们都可以对元素进行快速的查找。但一个重要的区别是:对平衡树的插入和删除往往很可能导致平衡树进行一次全局的调整。而对跳表的插入和删除只需要对整个数据结构的局部进行操作即可。这样带来的好处是:在高并发的情况下,你会需要一个全局锁来保证整个平衡树的线程安全。而对于跳表,你只需要部分锁即可。这样,在高并发环境下,你就可以拥有更好的性能。而就查询的性能而言,跳表的时间复杂度也是 所以在并发数据结构中,JDK 使用跳表来实现一个 Map。
跳表的本质是同时维护了多个链表,并且链表是分层的,最低层的链表维护了跳表内所有的元素,每上面一层链表都是下面一层的子集。
跳表内的所有链表的元素都是排序的。查找时,可以从顶级链表开始找。一旦发现被查找的元素大于当前链表中的取值,就会转入下一层链表继续找。这也就是说在查找过程中,搜索是跳跃式的。
从上面很容易看出,跳表是一种利用空间换时间的算法。
使用跳表实现 Map 和使用哈希算法实现 Map 的另外一个不同之处是:哈希并不会保存元素的顺序,而跳表内所有的元素都是排序的。因此在对跳表进行遍历时,你会得到一个有序的结果。所以,如果你的应用需要有序性,那么跳表就是你不二的选择。JDK 中实现这一数据结构的类是 ConcurrentSkipListMap。
Atomic Classes
Introduction to Atomic Classes
Atomic translates to “atomic” in Chinese. Here, Atomic refers to an operation that is non-interruptible. Even when executed by multiple threads together, once an operation starts, it will not be interrupted by other threads.
So, simply speaking, an atomic class is a class that has atomic/atomic-operation characteristics.
The atomic classes in the concurrency package java.util.concurrent are located under java.util.concurrent.atomic.
Based on the type of data being operated on, the atomic classes in the JUC package can be divided into four categories
-
Primitive Types
Atomically updating primitive types
AtomicInteger: Atomic integer classAtomicLong: Atomic long classAtomicBoolean: Atomic boolean class
-
Array Types
Atomically updating a specific element inside an array
AtomicIntegerArray: Atomic array class for integersAtomicLongArray: Atomic array class for longsAtomicReferenceArray: Atomic array class for reference types
-
Reference Types
AtomicReference: Atomic reference typeAtomicMarkableReference: Atomic update of a reference type with a mark. This class associates a boolean mark with a reference.AtomicStampedReference: Atomic update of a reference type with a stamp. This class associates an integer value with a reference and can be used to address the ABA problem that might occur when performing atomic updates with CAS.
Note:
AtomicMarkableReferencecannot solve the ABA problem. -
Updater Types for Object Fields
AtomicIntegerFieldUpdater: Updater for atomically updating integer fieldsAtomicLongFieldUpdater: Updater for atomically updating long fieldsAtomicReferenceFieldUpdater: Updater for atomically updating fields inside reference types
Primitive Type Atomic Classes
Atomically updating primitive types
AtomicInteger: Atomic integer classAtomicLong: Atomic long classAtomicBoolean: Atomic boolean class
The three classes above provide nearly the same methods, so here we use AtomicInteger as an example to illustrate.
Common Methods of the AtomicInteger Class
public final int get() // get the current valuepublic final int getAndSet(int newValue)// get the current value, then set a new valuepublic final int getAndIncrement()// get the current value, then incrementpublic final int getAndDecrement() // get the current value, then decrementpublic final int getAndAdd(int delta) // get the current value, then add the deltaboolean compareAndSet(int expect, int update) // if the input equals the expected value, atomically set to updatepublic final void lazySet(int newValue)// eventually set to newValue; after lazySet, other threads may read the old value for a short periodAtomicInteger Common usage:
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerTest {
public static void main(String[] args) { int temvalue = 0; AtomicInteger i = new AtomicInteger(0); temvalue = i.getAndSet(3); System.out.println("temvalue:" + temvalue + "; i:" + i); //temvalue:0; i:3 temvalue = i.getAndIncrement(); System.out.println("temvalue:" + temvalue + "; i:" + i); //temvalue:3; i:4 temvalue = i.getAndAdd(5); System.out.println("temvalue:" + temvalue + "; i:" + i); //temvalue:4; i:9 }}Advantages of Primitive Type Atomic Classes
Let’s look at the advantages of primitive type atomic classes with a simple example.
-
In a multi-threaded environment, not using atomic classes cannot guarantee thread safety (primitive types)
class Test {private volatile int count = 0;// To execute count++ in a thread-safe way, you need to lockpublic synchronized void increment() {count++;}public int getCount() {return count;}} -
In a multi-threaded environment, using atomic classes guarantees thread safety (primitive types)
class Test2 {private AtomicInteger count = new AtomicInteger();public void increment() {count.incrementAndGet();}// After using AtomicInteger, you don't need to lock, and you can still achieve thread safety.public int getCount() {return count.get();}}
A Brief Analysis of AtomicInteger Thread-Safety Principles
AtomicInteger source code excerpt:
// setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用) 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;AtomicInteger mainly relies on CAS (compare and swap), volatile, and native methods to guarantee atomic operations, thereby avoiding the high overhead of synchronized and significantly improving execution efficiency.
CAS works by comparing an expected value with the current value, and updating it to a new value only if they match. The Unsafe class’s objectFieldOffset() method is a native method used to obtain the memory address of the original value. In addition, value is a volatile variable and is visible in memory, so the JVM can ensure that any thread can obtain the latest value of this variable at any time.
Array-Type Atomic Classes
Atomically updating a single element in an array
AtomicIntegerArray: Atomic array class for integersAtomicLongArray: Atomic array class for longsAtomicReferenceArray: Atomic array class for reference types
The three classes above provide almost identical methods, so here we’ll use AtomicIntegerArray as an example.
AtomicIntegerArray Common Methods:
public final int get(int i) // get the value at index ipublic final int getAndSet(int i, int newValue)// return the current value at index i, and set it to newValuepublic final int getAndIncrement(int i)// get the value at index i, and increment that elementpublic final int getAndDecrement(int i) // get the value at index i, and decrement that elementpublic final int getAndAdd(int i, int delta) // get the value at index i, and add deltaboolean compareAndSet(int i, int expect, int update) // if the input value equals the expected, atomically set the index i element to updatepublic final void lazySet(int i, int newValue)// finally set the element at index i to newValue; may read the old value for a short time after thisAtomicIntegerArray Usage Example:
import java.util.concurrent.atomic.AtomicIntegerArray;
public class AtomicIntegerArrayTest {
public static void main(String[] args) { int temvalue = 0; int[] nums = { 1, 2, 3, 4, 5, 6 }; AtomicIntegerArray i = new AtomicIntegerArray(nums); for (int j = 0; j < nums.length; j++) { System.out.println(i.get(j)); } temvalue = i.getAndSet(0, 2); System.out.println("temvalue:" + temvalue + "; i:" + i); temvalue = i.getAndIncrement(0); System.out.println("temvalue:" + temvalue + "; i:" + i); temvalue = i.getAndAdd(0, 5); System.out.println("temvalue:" + temvalue + "; i:" + i); }
}Reference-Type Atomic Classes
Primitive type atomic classes can only update one variable. If you need to atomically update multiple variables, you should use reference-type atomic classes.
AtomicReference: Atomic reference typeAtomicStampedReference: Atomic update of a reference type with a stamp. This class associates an integer value with a reference and can be used to solve the ABA problem when performing atomic updates with CAS.AtomicMarkableReference: Atomic update of a reference type with a mark. This class associates a boolean mark with a reference
Usage Examples
AtomicReferenceClass:
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceTest {
public static void main(String[] args) { AtomicReference < Person > ar = new AtomicReference < Person > (); Person person = new Person("SnailClimb", 22); ar.set(person); Person updatePerson = new Person("Daisy", 20); ar.compareAndSet(person, updatePerson);
System.out.println(ar.get().getName()); System.out.println(ar.get().getAge()); }}
class Person { private String name; private int age;
public Person(String name, int age) { super(); this.name = name; this.age = age; }
public String getName() { return name; } public void setName(String name) { this.name = name; }
public int getAge() { return age; } public void setAge(int age) { this.age = age; }}The above code first creates a Person object, then puts it into an AtomicReference, and calls compareAndSet, which CAS-sets the ar if its value is person. If ar equals person, it is set to updatePerson. The implementation principle is the same as the compareAndSet method in AtomicInteger. The output after running the code is:Daisy20AtomicStampedReferenceClass Usage Example:
import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicStampedReferenceDemo { public static void main(String[] args) { // Instantiate, get current value and stamp final Integer initialRef = 0, initialStamp = 0; final AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(initialRef, initialStamp); System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp());
// compare and set final Integer newReference = 666, newStamp = 999; final boolean casResult = asr.compareAndSet(initialRef, newReference, initialStamp, newStamp); System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp() + ", casResult=" + casResult);
// Get current value and current stamp int[] arr = new int[1]; final Integer currentValue = asr.get(arr); final int currentStamp = arr[0]; System.out.println("currentValue=" + currentValue + ", currentStamp=" + currentStamp);
// Individually set the stamp final boolean attemptStampResult = asr.attemptStamp(newReference, 88); System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp() + ", attemptStampResult=" + attemptStampResult);
// Reset current value and stamp asr.set(initialRef, initialStamp); System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp());
// [Not recommended unless you fully understand the comments] // weak compare and set // Confusing! weakCompareAndSet ultimately calls compareAndSet. [Version: jdk-8u191] // but the comment says "May fail spuriously and does not provide ordering guarantees, // so is only rarely an appropriate alternative to compareAndSet." // todo Might be JVM forwarding in native method due to method name final boolean wCasResult = asr.weakCompareAndSet(initialRef, newReference, initialStamp, newStamp); System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp() + ", wCasResult=" + wCasResult); }}The output is as follows:
currentValue=0, currentStamp=0currentValue=666, currentStamp=999, casResult=truecurrentValue=666, currentStamp=999currentValue=666, currentStamp=88, attemptStampResult=truecurrentValue=0, currentStamp=0currentValue=666, currentStamp=999, wCasResult=trueAtomicMarkableReferenceClass Usage Example:
import java.util.concurrent.atomic.AtomicMarkableReference;
public class AtomicMarkableReferenceDemo { public static void main(String[] args) { // Instantiate, get current value and mark final Boolean initialRef = null, initialMark = false; final AtomicMarkableReference<Boolean> amr = new AtomicMarkableReference<>(initialRef, initialMark); System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked());
// compare and set final Boolean newReference1 = true, newMark1 = true; final boolean casResult = amr.compareAndSet(initialRef, newReference1, initialMark, newMark1); System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked() + ", casResult=" + casResult);
// Get current value and current mark boolean[] arr = new boolean[1]; final Boolean currentValue = amr.get(arr); final boolean currentMark = arr[0]; System.out.println("currentValue=" + currentValue + ", currentMark=" + currentMark);
// Individually set mark final boolean attemptMarkResult = amr.attemptMark(newReference1, false); System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked() + ", attemptMarkResult=" + attemptMarkResult);
// Reset current value and mark amr.set(initialRef, initialMark); System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked());
// [Not recommended unless you fully understand the comments] // weak compare and set // Confusing! weakCompareAndSet ultimately calls compareAndSet. [Version: jdk-8u191] // but the comment says "May fail spuriously and does not provide ordering guarantees, // so is only rarely an appropriate alternative to compareAndSet." // todo Might be JVM forwarding in native method due to method name final boolean wCasResult = amr.weakCompareAndSet(initialRef, newReference1, initialMark, newMark1); System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked() + ", wCasResult=" + wCasResult); }}The output is as follows:
currentValue=null, currentMark=falsecurrentValue=true, currentMark=true, casResult=truecurrentValue=true, currentMark=truecurrentValue=true, currentMark=false, attemptMarkResult=truecurrentValue=null, currentMark=falsecurrentValue=true, currentMark=true, wCasResult=trueAtomic Field Updaters for Object Fields
If you need to atomically update a field within a class, you should use the atomic field updater classes.
AtomicIntegerFieldUpdater: Updater for atomically updating integer fieldsAtomicLongFieldUpdater: Updater for atomically updating long fieldsAtomicReferenceFieldUpdater: Updater for atomically updating fields inside reference types
To atomically update an object’s field, two steps are required. First, because the atomic field updater classes are abstract, you must create an updater via the static method newUpdater(), specifying the class and the field to update. Second, the updated field must be declared as public volatile.
The three classes above provide almost identical methods, so here we’ll use AtomicIntegerFieldUpdater as an example.
Example usage of AtomicIntegerFieldUpdater:
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicIntegerFieldUpdaterTest { public static void main(String[] args) { AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
User user = new User("Java", 22); System.out.println(a.getAndIncrement(user));// 22 System.out.println(a.get(user));// 23 }}
class User { private String name; public volatile int age;
public User(String name, int age) { super(); this.name = name; this.age = age; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
Atomic原子クラス
Atomic原子クラスの紹介
Atomic は中国語で原子を意味します。ここでの Atomic は、操作が中断されないことを指します。複数のスレッドが同時に実行していても、ある操作を開始すれば他のスレッドに妨げられることはありません。
ですので、いわゆる原子クラスとは、基本的に原子性/原子操作の特性を持つクラスのことを指します。
並行性パッケージの java.util.concurrent の原子クラスはすべて java.util.concurrent.atomic に格納されています。
操作するデータ型に応じて、JUC パッケージの原子クラスを4つのカテゴリに分けられます。
-
基本データ型
基本データ型を原子性を用いて更新する
AtomicInteger:整数型原子クラスAtomicLong:長整数型原子クラスAtomicBoolean:布尔型原子クラス
-
配列型
配列の特定の要素を原子性を用いて更新する
AtomicIntegerArray:整型配列原子クラスAtomicLongArray:長整型配列原子クラスAtomicReferenceArray:参照型配列原子クラス
-
参照型
AtomicReference:参照型原子クラスAtomicMarkableReference:マーク付き参照型を原子更新するクラス。 boolean マークと参照を関連付けます。AtomicStampedReference:バージョン番号を持つ参照型を原子更新するクラス。整数値と参照を関連付け、原子更新データとデータのバージョン番号を解決するのに使用でき、CAS を用いた原子更新時に発生する可能性のある ABA 問題を解決できます。
注意:
AtomicMarkableReferenceは ABA 問題を解決できません。 -
オブジェクトの属性変更型
AtomicIntegerFieldUpdater:整数型フィールドを原子更新するアップデータAtomicLongFieldUpdater:長整形フィールドを原子更新するアップデータAtomicReferenceFieldUpdater:参照型フィールドを原子更新するアップデータ
基本データ型原子クラス
基本データ型を原子性を用いて更新する
AtomicInteger:整数型原子クラスAtomicLong:長整型原子クラスAtomicBoolean:ブール型原子クラス
上記の3つのクラスの提供するメソッドはほぼ同じなので、ここでは AtomicInteger を例として紹介します。
AtomicInteger クラスの一般的なメソッド
public final int get() //現在の値を取得public final int getAndSet(int newValue)//現在の値を取得し、新しい値を設定public final int getAndIncrement()//現在の値を取得し、インクリメントpublic final int getAndDecrement() //現在の値を取得し、デクリメントpublic final int getAndAdd(int delta) //現在の値を取得し、(delta)だけ加算boolean compareAndSet(int expect, int update) //期待値と実際の値が等しい場合、原子的にその値を update に設定public final void lazySet(int newValue)//最終的に newValue を設定。lazySet 後でも他のスレッドがしばらくの間旧値を読む可能性があります。AtomicInteger クラスの使用例 :
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerTest {
public static void main(String[] args) { int temvalue = 0; AtomicInteger i = new AtomicInteger(0); temvalue = i.getAndSet(3); System.out.println("temvalue:" + temvalue + "; i:" + i); //temvalue:0; i:3 temvalue = i.getAndIncrement(); System.out.println("temvalue:" + temvalue + "; i:" + i); //temvalue:3; i:4 temvalue = i.getAndAdd(5); System.out.println("temvalue:" + temvalue + "; i:" + i); //temvalue:4; i:9 }}基本データ型原子クラスの利点
簡単な例を通じて、基本データ型原子クラスの利点を見てみましょう。
-
マルチスレッド環境で原子クラスを使用せずにスレッドセーフを保証する(基本データ型)
class Test {private volatile int count = 0;// count++ をスレッドセーフに実行するにはロックが必要public synchronized void increment() {count++;}public int getCount() {return count;}} -
マルチスレッド環境で原子クラスを使用してスレッドセーフを保証する(基本データ型)
class Test2 {private AtomicInteger count = new AtomicInteger();public void increment() {count.incrementAndGet();}// AtomicInteger を使用すれば、ロックを取らなくてもスレッドセーフを実現できる。public int getCount() {return count.get();}}
AtomicInteger のスレッドセーフ原理の簡易分析
AtomicInteger クラスの一部ソースコード:
// 更新操作のために Unsafe.compareAndSwapInt を使用する設定 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;AtomicInteger クラスは主に CAS (compare and swap) + volatile およびネイティブメソッドを用いて原子操作を保証し、synchronized の高オーバーヘッドを回避して実行効率を大幅に向上させます。
CAS の原理は、期待値と現在値を比較し、同じであれば新しい値に更新することです。Unsafe クラスの objectFieldOffset() メソッドはネイティブメソッドで、元の値のメモリアドレスを取得するために使われます。さらに value は volatile 変数で、メモリ上で可視性が保証されるため、JVM は任意の時点で任意のスレッドがこの値の最新値を必ず取得できるようにします。
配列タイプ原子クラス
配列の特定の要素を原子性を用いて更新する
AtomicIntegerArray:整型配列原子クラスAtomicLongArray:長整型配列原子クラスAtomicReferenceArray:参照型配列原子クラス
上記の3つのクラスのメソッドはほぼ同じなので、ここでは AtomicIntegerArray を例として紹介します。
AtomicIntegerArray クラスの一般的なメソッド :
public final int get(int i) // index=i の位置要素の値を取得public final int getAndSet(int i, int newValue)// index=i の位置の現在の値を返し、これを newValue に設定public final int getAndIncrement(int i)// index=i の位置要素の値を取得し、同位置の要素を自動的にインクリメントpublic final int getAndDecrement(int i) // index=i の位置要素の値を取得し、同位置の要素をデクリメントpublic final int getAndAdd(int i, int delta) // index=i の位置要素の値を取得し、delta を加算boolean compareAndSet(int i, int expect, int update) // index=i の位置の要素値が expect に等しければ、原子更新して update に設定public final void lazySet(int i, int newValue)// 最終的に index=i の位置の要素を newValue に設定。lazySet を使用後、しばらくの間他スレッドが旧値を読む可能性があります。AtomicIntegerArray クラスの使用例 :
import java.util.concurrent.atomic.AtomicIntegerArray;
public class AtomicIntegerArrayTest {
public static void main(String[] args) { int temvalue = 0; int[] nums = { 1, 2, 3, 4, 5, 6 }; AtomicIntegerArray i = new AtomicIntegerArray(nums); for (int j = 0; j < nums.length; j++) { System.out.println(i.get(j)); } temvalue = i.getAndSet(0, 2); System.out.println("temvalue:" + temvalue + "; i:" + i); temvalue = i.getAndIncrement(0); System.out.println("temvalue:" + temvalue + "; i:" + i); temvalue = i.getAndAdd(0, 5); System.out.println("temvalue:" + temvalue + "; i:" + i); }
}参照型原子クラス
基本データ型原子クラスは変数1つを更新するだけですが、複数の変数を原子更新したい場合は参照型原子クラスを使用します。
AtomicReference:参照型原子クラスAtomicStampedReference:バージョン番号を持つ参照型を原子更新するクラス。整数値と参照を関連付け、原子更新データとデータのバージョン番号を解決するのに使用でき、CAS を用いた原子更新時に発生する可能性のある ABA 問題を解決できます。AtomicMarkableReference:マーク付き参照型を原子更新するクラス。 boolean マークと参照を関連付けます。
使用例
-
AtomicReferenceクラス:import java.util.concurrent.atomic.AtomicReference;public class AtomicReferenceTest {public static void main(String[] args) {AtomicReference < Person > ar = new AtomicReference < Person > ();Person person = new Person("SnailClimb", 22);ar.set(person);Person updatePerson = new Person("Daisy", 20);ar.compareAndSet(person, updatePerson);System.out.println(ar.get().getName());System.out.println(ar.get().getAge());}}class Person {private String name;private int age;public Person(String name, int age) {super();this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}}上記のコードはまず
Personオブジェクトを作成し、それをAtomicReferenceに設定します。その後compareAndSetメソッドを呼び出して ar の値を更新します。もし ar の値がpersonであれば、それをupdatePersonに設定します。実装原理はAtomicIntegerクラスのcompareAndSetメソッドと同じです。上記のコードを実行した場合の出力は次のとおりです。Daisy20 -
AtomicStampedReferenceクラスの使用例 :import java.util.concurrent.atomic.AtomicStampedReference;public class AtomicStampedReferenceDemo {public static void main(String[] args) {// 初期値とスタンプ値をインスタンス化final Integer initialRef = 0, initialStamp = 0;final AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(initialRef, initialStamp);System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp());// compare and setfinal Integer newReference = 666, newStamp = 999;final boolean casResult = asr.compareAndSet(initialRef, newReference, initialStamp, newStamp);System.out.println("currentValue=" + asr.getReference()+ ", currentStamp=" + asr.getStamp()+ ", casResult=" + casResult);// 現在の値と現在の stamp 値を取得int[] arr = new int[1];final Integer currentValue = asr.get(arr);final int currentStamp = arr[0];System.out.println("currentValue=" + currentValue + ", currentStamp=" + currentStamp);// stamp のみを個別に設定final boolean attemptStampResult = asr.attemptStamp(newReference, 88);System.out.println("currentValue=" + asr.getReference()+ ", currentStamp=" + asr.getStamp()+ ", attemptStampResult=" + attemptStampResult);// 現在の値と stamp を再設定asr.set(initialRef, initialStamp);System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp());// [推奨されません。コメントの意味を理解した場合のみ] weak compare and set// 混乱!weakCompareAndSet は結局 compareAndSet を呼び出します。[バージョン: jdk-8u191]// ただしコメントには「May fail spuriously and does not provide ordering guarantees,// so is only rarely an appropriate alternative to compareAndSet.」と書かれています。// もしかすると JVM がネイティブメソッドで転送しているのかもしれません。final boolean wCasResult = asr.weakCompareAndSet(initialRef, newReference, initialStamp, newStamp);System.out.println("currentValue=" + asr.getReference()+ ", currentStamp=" + asr.getStamp()+ ", wCasResult=" + wCasResult);}}出力結果は以下のとおりです:
currentValue=0, currentStamp=0currentValue=666, currentStamp=999, casResult=truecurrentValue=666, currentStamp=999currentValue=666, currentStamp=88, attemptStampResult=truecurrentValue=0, currentStamp=0currentValue=666, currentStamp=999, wCasResult=true -
AtomicMarkableReferenceクラスの使用例 :import java.util.concurrent.atomic.AtomicMarkableReference;public class AtomicMarkableReferenceDemo {public static void main(String[] args) {// 初期値とマーク値を取得final Boolean initialRef = null, initialMark = false;final AtomicMarkableReference<Boolean> amr = new AtomicMarkableReference<>(initialRef, initialMark);System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked());// compare and setfinal Boolean newReference1 = true, newMark1 = true;final boolean casResult = amr.compareAndSet(initialRef, newReference1, initialMark, newMark1);System.out.println("currentValue=" + amr.getReference()+ ", currentMark=" + amr.isMarked()+ ", casResult=" + casResult);// 現在の値と現在の mark 値を取得boolean[] arr = new boolean[1];final Boolean currentValue = amr.get(arr);final boolean currentMark = arr[0];System.out.println("currentValue=" + currentValue + ", currentMark=" + currentMark);// 単独で mark 値を設定final boolean attemptMarkResult = amr.attemptMark(newReference1, false);System.out.println("currentValue=" + amr.getReference()+ ", currentMark=" + amr.isMarked()+ ", attemptMarkResult=" + attemptMarkResult);// 現在の値と mark を再設定amr.set(initialRef, initialMark);System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked());// [推奨されません。コメントの意味を理解した場合のみ] weak compare and set// 混乱!weakCompareAndSet は結局 compareAndSet を呼び出します。// ただしコメントには「May fail spuriously and does not provide ordering guarantees,// so is only rarely an appropriate alternative to compareAndSet.」と書かれています。// もしかすると JVM がネイティブメソッドで転送しているのかもしれませんfinal boolean wCasResult = amr.weakCompareAndSet(initialRef, newReference1, initialMark, newMark1);System.out.println("currentValue=" + amr.getReference()+ ", currentMark=" + amr.isMarked()+ ", wCasResult=" + wCasResult);}}出力結果は以下のとおりです:
currentValue=null, currentMark=falsecurrentValue=true, currentMark=true, casResult=truecurrentValue=true, currentMark=truecurrentValue=true, currentMark=false, attemptMarkResult=truecurrentValue=null, currentMark=falsecurrentValue=true, currentMark=true, wCasResult=true
オブジェクト属性更新型原子クラス
オブジェクトのあるクラスの特定フィールドを原子更新したい場合は、オブジェクト属性更新型原子クラスを使用します。
AtomicIntegerFieldUpdater: 整数型フィールドを原子更新するアップデータAtomicLongFieldUpdater:長整型フィールドを原子更新するアップデータAtomicReferenceFieldUpdater:参照型フィールドを原子更新するアップデータ
オブジェクトの属性を原子更新したい場合は、2ステップが必要です。まず、これらの原子クラスはすべて抽象クラスなので、使用するたびに静的メソッド newUpdater() を用いて更新器を作成し、更新したいクラスと属性を設定します。次に、更新対象のオブジェクトの属性は public volatile 修飾子を持っている必要があります。
上記の3つのクラスのメソッドはほぼ同じなので、ここでは AtomicIntegerFieldUpdater を例として紹介します。
AtomicIntegerFieldUpdater クラスの使用例 :
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicIntegerFieldUpdaterTest { public static void main(String[] args) { AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
User user = new User("Java", 22); System.out.println(a.getAndIncrement(user));// 22 System.out.println(a.get(user));// 23 }}
class User { private String name; public volatile int age;
public User(String name, int age) { super(); this.name = name; this.age = age; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}よく使われる並行コンテナ
JDK が提供するこれらのコンテナの大半は java.util.concurrent パッケージにあります。
ConcurrentHashMap: スレッドセーフな HashMapCopyOnWriteArrayList: スレッドセーフな List、読み取りが多く書き込みが少ない場面で性能が非常に良く、Vectorを大きく上回ります。ConcurrentLinkedQueue: 高効率な并行キュー。リンクリストを用いて実装。これは非ブロッキングキューであり、スレッドセーフです。BlockingQueue: これはインタフェースで、JDK 内部ではリンクリストや配列などの方法でこのインタフェースを実装しています。ブロッキングキューを表し、データ共有チャネルとして非常に適しています。ConcurrentSkipListMap: 跳表の実装です。これは Map で、跳表のデータ構造を用いて高速な検索を実現します。
ConcurrentHashMap
HashMap はスレッドセーフではないことは周知の通りです。並行シナリオで実現可能な方法としては、Collections.synchronizedMap() メソッドで HashMap をラップする方法があります。しかし、これはグローバルなロックを使って異なるスレッド間の同時アクセスを同期させるため、無視できないパフォーマンス問題を招きます。
そこで生まれたのが HashMap のスレッドセーフ版である ConcurrentHashMap です。
JDK1.7 の時点では、ConcurrentHashMap は全体のバケツ配列を分割(Segment、セグメントロック)して、それぞれのロックがコンテナの一部データのみをロックします(下面の図参照)。複数スレッドがコンテナ内の異なるデータセグメントを読み書きしても、ロック競合が発生せず、並行アクセスが向上します。
JDK1.8 になると、ConcurrentHashMap は Segment の概念を捨て、代わりに Node 配列 + 連結リスト + 赤黒木のデータ構造で実装され、並行制御は synchronized と CAS を用いて操作します。(JDK1.6 以降 synchronized ロックには多くの最適化が施されています。)全体としては最適化されたスレッドセーフな HashMap のように見えます。JDK1.8 では Segment のデータ構造をまだ見ることができますが、属性はすでに簡略化され、旧バージョンとの互換性のためだけです。
CopyOnWriteArrayList
JDK1.5以前は、並行安全な List を使おうとすると Vector を使うしかありませんでした。しかし、Vector は旧式のコレクションで、ほとんどの操作で synchronized を用い、全体に大鎖をかけるような設計で、各メソッドの実行時にロックを獲得する必要があるため、性能は非常に低いです。
JDK1.5 で Java.util.concurrent(JUC)パッケージが導入され、スレッドセーフで並列性能の良いコンテナが多数提供されました。その中で唯一のスレッドセーフな List 実装が CopyOnWriteArrayList です。
ほとんどのビジネスシーンでは読み取り操作が書き込み操作よりも多いです。読み取り操作は元データを変更しないため、毎回読み取りにロックをかけるのは資源の無駄です。これに対して、内部データへ複数のスレッドが同時にアクセスできるようにするべきです。読み取り操作は安全だからです。
この考え方は ReentrantReadWriteLock の設計思想と非常に類似しており、読み取りは読み取り同士で競合せず、読み取りと書き込みは互いに排他的、書き込み同士のみ排他という原理です(読み取り同士は排他なし)。CopyOnWriteArrayList はこの思想をさらに実現しています。読み取りのパフォーマンスを最大化するため、CopyOnWriteArrayList の読み取り操作は完全にロック不要です。さらに、書き込み操作も読み取りをブロックしません。書き込み同士のみが排他されます。これにより読み取りの性能が大幅に向上します。
CopyOnWriteArrayList のスレッドセーフの核心は、コピーオンライト(Copy-On-Write)戦略を採用している点にあり、その名前からもそれが読み取れます。
更新(add、set、remove など)を行う際には、元の配列を直接変更せず、まず下層配列のコピーを作成し、コピー配列を変更してから変更後の配列を元の場所に再設定します。こうすることで、書き込み操作が読み取り操作を妨げることを回避します。
ConcurrentLinkedQueue
Java が提供するスレッドセーフな Queue は、ブロッキングキューとノンブロッキングキューに分けられます。ブロッキングキューの典型例は BlockingQueue、ノンブロッキングキューの典型例は ConcurrentLinkedQueue です。実際のアプリケーションでは、ニーズに応じてブロッキングキューとノンブロッキングキューを選択します。ブロッキングキューはロックで実現、ノンブロッキングキューは CAS 操作で実現。
名前から分かる通り、ConcurrentLinkedQueue は内部データ構造としてリンクリストを使用します。ConcurrentLinkedQueue は高い同時実行環境で最も性能の良いキューの1つと考えられます。その高性能の理由は、内部の複雑な実装にあります。
ConcurrentLinkedQueue の内部コードについてはここでは分析しません。主に CAS 非ブロッキングアルゴリズムを用いてスレッドセーフを実現していることだけを知っていれば十分です。
ConcurrentLinkedQueue は、性能要件が相対的に高く、キューの読み書きが複数のスレッドにより同時に行われる状況に適しています。つまり、キューにロックをかけるコストが高い場合にはロックレスの ConcurrentLinkedQueue を代替として使用するのが適しています。
BlockingQueue
BlockingQueue の概要
前述のとおり、ConcurrentLinkedQueue は高性能な非ブロッキングキューです。ここからはブロッキングキューである BlockingQueue について説明します。BlockingQueue は広く「生産者-消費者」問題で用いられ、ブロック可能な挿入と削除のメソッドを提供します。キューが満杯のとき、生産者スレッドはブロックされ、キューに空きができるまで待機します。キューが空のとき、消費者スレッドはブロックされ、キューが非空になるまで待機します。
BlockingQueue はインタフェースで、Queue を継承しています。従って、その実装クラスも Queue の実装として使用でき、Queue は Collection インタフェースを継承します。以下は BlockingQueue の関連実装クラスです。
以下では、3つの一般的な BlockingQueue の実装クラス:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue について主に説明します。
ArrayBlockingQueue
ArrayBlockingQueue は BlockingQueue インタフェースの有界キュー実装クラスで、底層は配列を用いて実装されています。
public class ArrayBlockingQueue<E>extends AbstractQueue<E>implements BlockingQueue<E>, Serializable{}ArrayBlockingQueue は作成後、容量を変更できません。その並行制御は再入可能ロック ReentrantLock を用い、挿入操作でも読み取り操作でもロックを取得してから操作を行います。キューの容量が満杯の場合、要素を挿入しようとすると操作がブロックされ、空のキューから要素を取り出そうとしても同様にブロックされます。
ArrayBlockingQueue はデフォルトではスレッドがキューへアクセスする公平性を保証しません。公平性とは、スレッドの待機時間の絶対的な順序に厳密に従うこと、つまり最初に待っていたスレッドが最初に ArrayBlockingQueue にアクセスできることを指します。一方、公平性を保証しない場合は、アクセスの順序が厳密な時間順にはなりません。長時間待機しているスレッドがアクセスできないこともあります。公平性を保証する ArrayBlockingQueue を得たい場合は、以下のコードを使用します:
private static ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(10,true);LinkedBlockingQueue
LinkedBlockingQueue は一方向リンクリストを基盤とするブロッキングキューです。無限大に近いキューとして使うことも、有界キューとして使うこともできます。FIFO を満たし、ArrayBlockingQueue に比べてスループットが高くなります。ただし、容量が急速に増えると大量のメモリを消費します。通常、LinkedBlockingQueue オブジェクトを作成する際にはサイズを指定します。未指定の場合、容量は Integer.MAX_VALUE となります。
関連コンストラクタ:
/** *ある意味での無界キュー * Creates a {@code LinkedBlockingQueue} with a capacity of * {@link Integer#MAX_VALUE}. */ public LinkedBlockingQueue() { this(Integer.MAX_VALUE); }
/** *有界キュー * Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity. * * @param capacity the capacity of this queue * @throws IllegalArgumentException if {@code capacity} is not greater * than zero */ public LinkedBlockingQueue(int capacity) { if (capacity <= 0) throw new IllegalArgumentException(); this.capacity = capacity; last = head = new Node<E>(null); }PriorityBlockingQueue
PriorityBlockingQueue は優先度をサポートする無限ブロッキングキューです。デフォルトでは要素は自然順序でソートされ、あるいは Comparable を実装したクラスを用いてソート規則を指定することができます。また、初期化時にコンストラクタ引数 Comparator を用いてソート規則を指定することもできます。
PriorityBlockingQueue の同期制御は再入可能ロック ReentrantLock を用い、キューは無界です(ArrayBlockingQueue は有界、LinkedBlockingQueue も構築時に容量を指定して最大容量を設定可能ですが、PriorityBlockingQueue は初期のキューサイズのみを指定でき、後から要素を挿入する際には容量が足りない場合自動的に拡張されます)。
簡単に言えば、それは PriorityQueue のスレッドセーフ版です。null 値の挿入はできず、挿入されるオブジェクトは比較可能でなければならず、そうでない場合は ClassCastException が発生します。挿入操作の put はブロックされません。無界キューなので(take はキューが空のときにブロックします)。
ConcurrentSkipListMap
跳表を導入する前に、まず跳表を簡単に理解しておきましょう。
単方向リストは有序であっても、データを検索したい場合は先頭から末尾まで走査する必要があり、効率は低いです。跳表は高速な検索を可能にするデータ構造で、平衡木に似ています。要素を高速に検索できます。しかし大きな違いは、平衡木の挿入・削除は全体の再調整を伴うことが多いのに対し、跳表の挿入・削除はデータ構造の局所的な操作のみで済む点です。これにより、並行時には全体をロックして平衡木のスレッドセーフを保証する必要が出てくる一方、跳表では一部のロックだけで済むことが多く、性能が向上します。検索の時間計算量は であり、並行データ構造の Map を実装する際には跳表が用いられています。JDK でこのデータ構造を実装しているクラスは ConcurrentSkipListMap です。
部分信息可能已经过时









