一、 原子操作类介绍
在并发编程中很容易出现并发安全的问题,比如多个线程执行i++操作,就有可能获取不到正确的值,而这个问题,最常用的方法是通过Synchronized进行控制来达到线程安全的目的。但是由于synchronized是采用的是悲观锁策略,并不是特别高效的一种解决方案。实际上,在java.util.concurrent包下的atomic包提供了一系列的操作简单,性能高效,并能保证线程安全
的类去更新基本类型变量,数组元素,引用类型以及更新对象中的字段类型。atomic包下的这些类都是采用的是乐观锁策略去原子更新数据,在java中则是使用CAS操作具体实现。
二、 预备知识--CAS算法
能够弄懂atomic包下这些原子操作类的实现原理,就要先明白什么是CAS算法。
什么是CAS?
使用锁时,线程获取锁是一种悲观锁策略,即假设每一次执行临界区代码都会产生冲突,所以当前线程获取到锁的时候同时也会阻塞其他线程获取该锁。而CAS机制(又称为无锁操作)是一种乐观锁策略,它假设所有线程访问共享资源的时候不会出现冲突,既然不会出现冲突自然而然就不会阻塞其他线程的操作。因此,线程就不会出现阻塞停顿的状态。那么,如果出现冲突了怎么办?无锁操作是使用CAS(compare and swap)又叫做比较交换来鉴别线程是否出现冲突,出现冲突就重试当前操作直到没有冲突为止。
CAS的实现需要硬件指令集的支撑,使得检测和交换作为原子操作。
Synchronized VS CAS
元老级的Synchronized(未优化前)最主要的问题是:在存在线程竞争的情况下会出现线程阻塞和唤醒锁带来的性能问题,因为这是一种互斥同步(阻塞同步)。而CAS并不是武断的将线程挂起,当CAS操作失败后会进行一定的尝试,而非进行耗时的挂起唤醒的操作,因此也叫做非阻塞同步。这是两者主要的区别。
使用CAS时非阻塞同步,也就是说不会将线程挂起,会自旋(无非就是一个死循环)进行下一次尝试,如果这里自旋时间过长对性能是很大的消耗。如果JVM能支持处理器提供的pause指令,那么在效率上会有一定的提升。
三、原子类的分类
原子更新基本类型(3个)
AtomicBoolean 原子更新布尔类型
AtomicInteger 原子更新整型
AtomicLong 原子更新长整型
原子更新数组(3个)
AtomicIntegerArray 原子更新整形数组中的元素
AtomicLongArray 原子更新长整型数组中的元素
AtomicReferenceArray 原子更新引用类型数组中的元素
原子更新引用类型(3个)
AtomicReference 原子更新引用类型
AtomicMarkableReference 原子更新带有标记位的引用类型
AtomicStampedReference 原子更新带有版本号的引用类型,。而为什么在更新的时候会带有版本号,是为了解决CAS的ABA问题
原子更新字段类(3个)
AtomicIntegerFieldUpdater 原子更新整形字段
AtomicLongFieldUpdater 原子更新长整型字段
AtomicReferenceFieldUpdater 原子更新引用类型字段
对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater。
四、原子更新基本类型
使用原子方式更新基本类型,共包括3个类:
AtomicBoolean:原子更新布尔变量
AtomicInteger:原子更新整型变量
AtomicLong:原子更新长整型变量
常用的方法如下,以AtomicInteger源码为例进行说明(JDK 1.7),其他2个类似:
public class AtomicInteger extends Number implements java.io.Serializable {
private volatile int value;
/**
* Creates a new AtomicInteger with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicInteger(int initialValue) {
value = initialValue;
}
/**
* Creates a new AtomicInteger with initial value {@code 0}.
*/
public AtomicInteger() {
}
//返回当前值
public final int get() {
return value;
}
//设置新的值,非原子操作
public final void set(int newValue) {
value = newValue;
}
//最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值
public final void lazySet(int newValue) {
unsafe.putOrderedInt(this, valueOffset, newValue);
}
//原子操作,取当前的值,并设置新的值,返回旧值
public final int getAndSet(int newValue) {
for (;;) { //自旋
int current = get();
if (compareAndSet(current, newValue))
return current;
}
}
//如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
} //unsafe类提供的是硬件级别的方法,是CSA原语,即检测和更新是原子操作
//如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
//以原子方式将当前值加 1,返回旧值
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
//以原子方式将当前值减 1,返回旧值
public final int getAndDecrement() {
for (;;) {
int current = get();
int next = current - 1;
if (compareAndSet(current, next))
return current;
}
}
//以原子方式将给定值与当前值相加,返回旧值
public final int getAndAdd(int delta) {
for (;;) {
int current = get();
int next = current + delta;
if (compareAndSet(current, next))
return current;
}
}
//以原子方式将当前值加 1。
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
//以原子方式将当前值减 1,返回新值
public final int decrementAndGet() {
for (;;) {
int current = get();
int next = current - 1;
if (compareAndSet(current, next))
return next;
}
}
//以原子方式将给定值与当前值相加,返回新值
public final int addAndGet(int delta) {
for (;;) {
int current = get();
int next = current + delta;
if (compareAndSet(current, next))
return next;
}
}
}
其中LazySet()方法比较特殊,关于该方法的更多信息可以参考并发网翻译的一篇文章《AtomicLong.lazySet是如何工作的?》
使用例子
多线程情况下,对AtomicInteger的原子自增操作。
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerTest {
private static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void increase(){
atomicInteger.incrementAndGet();
}
public static void main(String[] args){
for (int i = 0; i < 5; i++){
new Thread(new Runnable() {
public void run() {
for(int j=0;j<1000;j++)
increase();
}
}).start();
}
while(Thread.activeCount()>1)
Thread.yield();
System.out.println(atomicInteger.get());
}
}
在多线程的情况下,得到的结果是正确的5*1000,但是如果仅仅使用int类型的成员变量则可能得到不同的结果。这里的关键在于getAndIncrement是原子操作compareAndSet(current, next)。
五、原子更新数组
通过原子更新数组里的某个元素,共有3个类:
AtomicIntegerArray:原子更新整型数组的某个元素
AtomicLongArray:原子更新长整型数组的某个元素
AtomicReferenceArray:原子更新引用类型数组的某个元素
AtomicIntegerArray为例讲解,其他2和类似,部分源码如下(JDK 1.7):
public class AtomicIntegerArray implements java.io.Serializable {
private final int[] array;
/**
* Creates a new AtomicIntegerArray of the given length, with all
* elements initially zero.
*
* @param length the length of the array
*/
public AtomicIntegerArray(int length) {
array = new int[length];
}
/**
* Creates a new AtomicIntegerArray with the same length as, and
* all elements copied from, the given array.
*
* @param array the array to copy elements from
* @throws NullPointerException if array is null
*/
public AtomicIntegerArray(int[] array) {
// Visibility guaranteed by final field guarantees
this.array = array.clone();
}
/**
* Returns the length of the array.
*
* @return the length of the array
*/
public final int length() {
return array.length;
}
/**
* Gets the current value at position {@code i}.
*
* @param i the index
* @return the current value
*/
public final int get(int i) {
return getRaw(checkedByteOffset(i));
}
private int getRaw(long offset) {
return unsafe.getIntVolatile(array, offset);
}
/**
* Sets the element at position {@code i} to the given value.
*
* @param i the index
* @param newValue the new value
*/
public final void set(int i, int newValue) {
unsafe.putIntVolatile(array, checkedByteOffset(i), newValue);
}
/**
* Eventually sets the element at position {@code i} to the given value.
*
* @param i the index
* @param newValue the new value
* @since 1.6
*/
public final void lazySet(int i, int newValue) {
unsafe.putOrderedInt(array, checkedByteOffset(i), newValue);
}
/**
* Atomically sets the element at position {@code i} to the given
* value and returns the old value.
*
* @param i the index
* @param newValue the new value
* @return the previous value
*/
public final int getAndSet(int i, int newValue) {
long offset = checkedByteOffset(i);
while (true) {
int current = getRaw(offset);
if (compareAndSetRaw(offset, current, newValue))
return current;
}
}
/**
* Atomically sets the element at position {@code i} to the given
* updated value if the current value {@code ==} the expected value.
*
* @param i the index
* @param expect the expected value
* @param update the new value
* @return true if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(int i, int expect, int update) {
return compareAndSetRaw(checkedByteOffset(i), expect, update);
}
private boolean compareAndSetRaw(long offset, int expect, int update) {
return unsafe.compareAndSwapInt(array, offset, expect, update);
}
}
使用例子:
多线程情况下,对数组的每个值都自增,这里使用Executor框架管理线程,代码如下:
public class AtomicIntegerArrayDemo {
02 staticAtomicIntegerArray arr = new AtomicIntegerArray(10);
03 public staticclass AddThread implements Runnable{
04 publicvoid run(){
05 for(intk=0;k<10000;k++)
06 arr.getAndIncrement(k%arr.length());
07 }
08 }
09 public staticvoid main(String[] args) throws InterruptedException {
10 Thread[]ts=new Thread[10];
11 for(intk=0;k<10;k++){
12 ts[k]=new Thread(new AddThread());
13 }
14 for(intk=0;k<10;k++){ts[k].start();}
15 for(intk=0;k<10;k++){ts[k].join();}
16 System.out.println(arr);
17 }
18 }
上述代码第2行,申明了一个内含10个元素的数组。第3行定义的线程对数组内10个元素进行累加操作,每个元素各加1000次。第11行,开启10个这样的线程。因此,可以预测,如果线程安全,数组内10个元素的值必然都是10000。反之,如果线程不安全,则部分或者全部数值会小于10000。
程序的输出结果如下:
[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000,10000, 10000]
这说明AtomicIntegerArray确实合理地保证了数组的线程安全性。
六、原子更新引用类型
需要更新引用类型往往涉及多个变量,早atomic包有三个类:
AtomicReference 原子更新引用类型
AtomicMarkableReference 原子更新带有标记位的引用类型
AtomicStampedReference 原子更新带有版本号的引用类型,。而为什么在更新的时候会带有版本号,是为了解决CAS的ABA问题
下面以AtomicReference为例进行说明:
提供了如下的方法:
compareAndSet(V expect, V update):Atomically sets the value to the given updated value if the current value == the expected value.
getAndSet(V newValue):Atomically sets to the given value and returns the old value.
lazySet(V newValue):Eventually sets to the given value.
set(V newValue):Sets to the given value.
get():Gets the current value.
class Person {
private String name;
private int age;
public Person(String name, int age) {
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;
}
public String toString() {
return "[name: " + this.name + ", age: " + this.age + "]";
}
}
如果使用普通的对象引用,例如:
一个对象的初始状态为 name=Tom, age = 18。
在 线程1 中将 name 修改为 Tom1,age + 1。
在 线程2 中将 name 修改为 Tom2,age + 2。
我们认为只会产生两种结果:
若 线程1 先执行,线程2 后执行,则中间状态为 name=Tom1, age = 19,结果状态为 name=Tom2, age = 21
若 线程2 先执行,线程1 后执行,则中间状态为 name=Tom2, age = 20,结果状态为 name=Tom1, age = 21
在多线程情况下进行对象的更新可能会导致不一致性。
// 普通引用
private static Person person;
public static void main(String[] args) throws InterruptedException {
person = new Person("Tom", 18);
System.out.println("Person is " + person.toString());
Thread t1 = new Thread(new Task1());
Thread t2 = new Thread(new Task2());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Now Person is " + person.toString());
}
static class Task1 implements Runnable {
public void run() {
person.setAge(person.getAge() + 1);
person.setName("Tom1");
System.out.println("Thread1 Values "
+ person.toString());
}
}
static class Task2 implements Runnable {
public void run() {
person.setAge(person.getAge() + 2);
person.setName("Tom2");
System.out.println("Thread2 Values "
+ person.toString());
}
}
可能的输出如下:
Person is [name: Tom, age: 18]
Thread2 Values [name: Tom1, age: 21]
Thread1 Values [name: Tom1, age: 21]
Now Person is [name: Tom1, age: 21]
如果使用原子性对象引用,在多线程情况下进行对象的更新可以确保一致性。例如:
// 普通引用
private static Person person;
// 原子性引用
private static AtomicReference<Person> aRperson;
public static void main(String[] args) throws InterruptedException {
person = new Person("Tom", 18);
aRperson = new AtomicReference<Person>(person);
System.out.println("Atomic Person is " + aRperson.get().toString());
Thread t1 = new Thread(new Task1());
Thread t2 = new Thread(new Task2());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Now Atomic Person is " + aRperson.get().toString());
}
static class Task1 implements Runnable {
public void run() {
aRperson.getAndSet(new Person("Tom1", aRperson.get().getAge() + 1));
System.out.println("Thread1 Atomic References "
+ aRperson.get().toString());
}
}
static class Task2 implements Runnable {
public void run() {
aRperson.getAndSet(new Person("Tom2", aRperson.get().getAge() + 2));
System.out.println("Thread2 Atomic References "
+ aRperson.get().toString());
}
}
可能的输出如下:
Atomic Person is [name: Tom, age: 18]
Thread1 Atomic References [name: Tom1, age: 19]
Thread2 Atomic References [name: Tom2, age: 21]
Now Atomic Person is [name: Tom2, age: 21]
七、原子更新字段类
如果需要原子更新某个类的某个字段,就需要用到原子更新字段类,可以使用以下几个类:
AtomicIntegerFieldUpdater 原子更新整形字段
AtomicLongFieldUpdater 原子更新长整型字段
AtomicReferenceFieldUpdater 原子更新引用类型字段
对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater。
要想原子更新字段,需要两个步骤:
每次必须使用newUpdater创建一个更新器,并且需要设置想要更新的类的字段
更新类的字段(属性)必须为public volatile
下面的代码演示如何使用原子更新字段类更新字段:
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicIntegerFieldUpdaterDemo {
//创建一个原子更新器
private static AtomicIntegerFieldUpdater<User> atomicIntegerFieldUpdater =
AtomicIntegerFieldUpdater.newUpdater(User.class,"old");
public static void main(String[] args){
User user = new User("Tom",15);
//原来的年龄
System.out.println(atomicIntegerFieldUpdater.getAndIncrement(user));
//现在的年龄
System.out.println(atomicIntegerFieldUpdater.get(user));
}
static class User{
private String name;
public volatile int old;
public User(String name, int old) {
this.name = name;
this.old = old;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getOld() {
return old;
}
public void setOld(int old) {
this.old = old;
}
}
}