目录[-]
一.java并发包下的原子变量综述
在Java1.5中就引入了原子变量,它提供对单个变量的原子操作。当你在操作一个普通变量时,你在Java实现的每个操作,在程序编译时会被转换成几个机器能读懂的指令。例如,当你分配一个值给变量,在Java你只使用了一个指令,但是当你编译这个程序时,这个指令就被转换成多个JVM语言指令。这样子的话当你在操作多个线程且共享一个变量时,就会导致数据不一致的错误。为了避免这样的问题,Java引入了原子变量。当一个线程正在操作一个原子变量时,即使其他线程也想要操作这个变量,类的实现中含有一个检查那步骤操作是否完成的机制。 基本上,操作获取变量的值,改变本地变量值,然后尝试以新值代替旧值。如果旧值还是一样,那么就改变它。如果不一样,方法再次开始操作。这个操作称为 Compare and Set(校对注:简称CAS,比较并交换的意思)。
原子变量不使用任何锁或者其他同步机制来保护它们的值的访问。他们的全部操作都是基于CAS操作。它保证几个线程可以同时操作一个原子对象也不会出现数据不一致的错误,并且它的性能比使用受同步机制保护的正常变量要好。
二.Atomic包介绍
在Atomic包里一共有12个类,四种原子更新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新字段。Atomic包里的类基本都是使用Unsafe实现的包装类。
- 1. 原子更新基本类型: AtomicInteger, AtomicLong, AtomicBoolean ;
- 2. 原子更新数组: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray ;
- 3. 原子更新引用: AtomicReference, AtomicStampedReference, AtomicMarkableReference ;
- 4. 原子更新字段: AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater。
三.原子更新基本类型
因为大体相同,以一个作为示例.AtomicLong是作用是对长整形进行原子操作。
在32位操作系统中,64位的long 和 double 变量由于会被JVM当作两个分离的32位来进行操作,所以不具有原子性。而使用AtomicLong能让long的操作保持原子型。
3.1 函数列表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
// 构造函数
AtomicLong()
// 创建值为initialValue的AtomicLong对象
AtomicLong(
long
initialValue)
// 以原子方式设置当前值为newValue。
final
void
set(
long
newValue)
// 获取当前值
final
long
get()
// 以原子方式将当前值减 1,并返回减1后的值。等价于“--num”
final
long
decrementAndGet()
// 以原子方式将当前值减 1,并返回减1前的值。等价于“num--”
final
long
getAndDecrement()
// 以原子方式将当前值加 1,并返回加1后的值。等价于“++num”
final
long
incrementAndGet()
// 以原子方式将当前值加 1,并返回加1前的值。等价于“num++”
final
long
getAndIncrement()
// 以原子方式将delta与当前值相加,并返回相加后的值。
final
long
addAndGet(
long
delta)
// 以原子方式将delta添加到当前值,并返回相加前的值。
final
long
getAndAdd(
long
delta)
// 如果当前值 == expect,则以原子方式将该值设置为update。成功返回true,否则返回false,并且不修改原值。
final
boolean
compareAndSet(
long
expect,
long
update)
// 以原子方式设置当前值为newValue,并返回旧值。
final
long
getAndSet(
long
newValue)
// 返回当前值对应的int值
int
intValue()
// 获取当前值对应的long值
long
longValue()
// 以 float 形式返回当前值
float
floatValue()
// 以 double 形式返回当前值
double
doubleValue()
// 最后设置为给定值。延时设置变量值,这个等价于set()方法,但是由于字段是volatile类型的,因此次字段的修改会比普通字段(非volatile字段)有稍微的性能延时(尽管可以忽略),所以如果不是想立即读取设置的新值,允许在“后台”修改值,那么此方法就很有用。如果还是难以理解,这里就类似于启动一个后台线程如执行修改新值的任务,原线程就不等待修改结果立即返回(这种解释其实是不正确的,但是可以这么理解)。
final
void
lazySet(
long
newValue)
// 如果当前值 == 预期值,则以原子方式将该设置为给定的更新值。JSR规范中说:以原子方式读取和有条件地写入变量但不 创建任何 happen-before 排序,因此不提供与除 weakCompareAndSet 目标外任何变量以前或后续读取或写入操作有关的任何保证。大意就是说调用weakCompareAndSet时并不能保证不存在happen-before的发生(也就是可能存在指令重排序导致此操作失败)。但是从Java源码来看,其实此方法并没有实现JSR规范的要求,最后效果和compareAndSet是等效的,都调用了unsafe.compareAndSwapInt()完成操作。
final
boolean
weakCompareAndSet(
long
expect,
long
update)
|
常用方法如下:
int addAndGet(int delta) :以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果boolean compareAndSet(int expect, int update) :如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。
int getAndIncrement():以原子方式将当前值加1,注意:这里返回的是自增前的值。
void lazySet(int newValue):见2.3.
int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值。
3.2 AtomicLong.lazySet是如何工作的?
为一个AtomicLong对象设置一个值,jvm会确保其他线程读取到最新值,原子类和voliatile变量也是一样的,这是由依赖于硬件的系统指令(如x86的xchg)实现的。lazySet却是无法保证这一点的方法,所以其他线程在之后的一小段时间里还是可以读到旧的值。在多核处理器下,内存以及cpu缓存的读和写常常是顺序执行的,所以在多个cpu缓存之间同步一个内存值的代价是很昂贵的。这样做是提高了性能.
大多数的原子类,比如AtomicLong本质上都是一个Unsafe和一个volatile Long变量的包装类.
3.3 其他基本类型怎么办?
Atomic包提供了三种基本类型的原子更新,但是Java的基本类型里还有char,float和double等。那么问题来了,如何原子的更新其他的基本类型呢?Atomic包里的类基本都是使用Unsafe实现的,让我们一起看下Unsafe的源码,发现Unsafe只提供了三种CAS方法,compareAndSwapObject,compareAndSwapInt和compareAndSwapLong,再看AtomicBoolean源码,发现其是先把Boolean转换成整型,再使用compareAndSwapInt进行CAS,所以原子更新double也可以用类似的思路来实现。3.4 AtomicLong 使用示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
package
org.credo.jdk.thread.atomic;
import
java.util.concurrent.ExecutorService;
import
java.util.concurrent.Executors;
import
java.util.concurrent.atomic.AtomicLong;
import
java.util.concurrent.locks.Lock;
import
java.util.concurrent.locks.ReentrantLock;
public
class
TestAtomicLong
implements
Runnable
{
public
static
AtomicLong atomicLong =
new
AtomicLong(1000L);
private
String operator;
// 操作人
private
long
number;
// 操作数额
private
Lock lock;
public
TestAtomicLong(String operator,
long
number, Lock lock)
{
this
.operator = operator;
this
.number = number;
this
.lock = lock;
}
@Override
public
void
run()
{
long
result = atomicLong.addAndGet(number);
System.out.println(Thread.currentThread().getName() +
": "
+ operator +
"执行了"
+ number +
",当前余额:"
+ result);
System.out.println(Thread.currentThread().getName() +
": "
+ operator +
"+1-->"
+ atomicLong.addAndGet(
1
));
System.out.println(Thread.currentThread().getName() +
": "
+ operator + atomicLong.toString());
}
public
static
void
main(String[] args)
{
ExecutorService pool = Executors.newFixedThreadPool(
2
);
Lock lock =
new
ReentrantLock(
false
);
Runnable t1 =
new
TestAtomicLong(
"张三"
,
100
, lock);
Runnable t2 =
new
TestAtomicLong(
"李四"
,
200
, lock);
Runnable t3 =
new
TestAtomicLong(
"王五"
,
1000
, lock);
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.shutdown();
}
}
|
运行结果:
pool-1-thread-2: 李四执行了200,当前余额:1300
pool-1-thread-1: 张三执行了100,当前余额:1100
pool-1-thread-2: 李四+1-->1301
pool-1-thread-1: 张三+1-->1302
pool-1-thread-2: 李四1302
pool-1-thread-1: 张三1302
pool-1-thread-2: 王五执行了1000,当前余额:2302
pool-1-thread-2: 王五+1-->2303
pool-1-thread-2: 王五2303
结果是我们预见的2303.
如果我们修改AtomicLong atomicLong 为普通的Long类型[public static Long atomicLong = new Long(1000L);],会发送什么?!同时我们修改RUN方法
1
2
3
4
5
6
7
8
|
@Override
public
void
run()
{
long
result = atomicLong+(number);
System.out.println(Thread.currentThread().getName() +
": "
+ operator +
"执行了"
+ number +
",当前余额:"
+ result);
//System.out.println(Thread.currentThread().getName() + ": " + operator + "+1-->" + atomicLong.addAndGet(1));
System.out.println(Thread.currentThread().getName() +
": "
+ operator + atomicLong.toString());
}
|
执行结果,完全是错乱的:
pool-1-thread-1: 张三1000
pool-1-thread-1: 王五执行了1000,当前余额:2000
pool-1-thread-1: 王五1000
pool-1-thread-2: 李四执行了200,当前余额:1200
pool-1-thread-2: 李四1000
四:原子更新数组
AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray类进一步扩展了原子操作,对这些类型的数组提供了支持。这些类在为其数组元素提供 volatile 访问语义方面也引人注目,这对于普通数组来说是不受支持的。他们内部并不是像AtomicInteger一样维持一个volatile变量,而是全部由native方法实现,4.1 AtomicLongArray函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
// 创建给定长度的新 AtomicLongArray。
AtomicLongArray(
int
length)
// 创建与给定数组具有相同长度的新 AtomicLongArray,并从给定数组复制其所有元素。
AtomicLongArray(
long
[] array)
// 以原子方式将给定值增加到索引 i 的元素。
long
addAndGet(
int
i,
long
delta)
// 如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
boolean
compareAndSet(
int
i,
long
expect,
long
update)
// 以原子方式将索引 i 的元素减1。
long
decrementAndGet(
int
i)
// 获取位置 i 的当前值。
long
get(
int
i)
// 以原子方式将给定值与索引 i 的元素相加。
long
getAndAdd(
int
i,
long
delta)
// 以原子方式将索引 i 的元素减 1。
long
getAndDecrement(
int
i)
// 以原子方式将索引 i 的元素加 1。
long
getAndIncrement(
int
i)
// 以原子方式将位置 i 的元素设置为给定值,并返回旧值。
long
getAndSet(
int
i,
long
newValue)
// 以原子方式将索引 i 的元素加1。
long
incrementAndGet(
int
i)
// 最终将位置 i 的元素设置为给定值。
void
lazySet(
int
i,
long
newValue)
// 返回该数组的长度。
int
length()
// 将位置 i 的元素设置为给定值。
void
set(
int
i,
long
newValue)
// 返回数组当前值的字符串表示形式。
String toString()
// 如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
boolean
weakCompareAndSet(
int
i,
long
expect,
long
update)
|
4.2 AtomicIntegerArray的实现片断:
1
2
3
4
5
6
7
8
9
10
|
private
static
final
Unsafe unsafe = Unsafe.getUnsafe();
private
static
final
int
base = unsafe.arrayBaseOffset(
int
[].
class
);
private
static
final
int
scale = unsafe.arrayIndexScale(
int
[].
class
);
private
final
int
[] array;
public
final
int
get(
int
i) {
return
unsafe.getIntVolatile(array, rawIndex(i));
}
public
final
void
set(
int
i,
int
newValue) {
unsafe.putIntVolatile(array, rawIndex(i), newValue);
}
|
4.3 AtimicLongArray示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
public
static
AtomicLongArray atomicLongArray =
new
AtomicLongArray(
new
long
[]{
0
,
0
,
0
});
private
String operator;
// 操作人
private
long
number;
// 操作数额
public
TestAtomicLong(String operator,
long
number)
{
this
.operator = operator;
this
.number = number;
}
@Override
public
void
run()
{
atomicLongArray.addAndGet(
0
, number);
atomicLongArray.getAndIncrement(
0
);
atomicLongArray.set(
2
, number);
System.out.println(Thread.currentThread().getName() +
": "
+ operator +
"执行了"
+ number +
",当前:"
+ atomicLongArray.toString());
}
public
static
void
main(String[] args)
{
ExecutorService pool = Executors.newFixedThreadPool(
2
);
Runnable t1 =
new
TestAtomicLong(
"张三"
,
1
);
Runnable t2 =
new
TestAtomicLong(
"李四"
,
2
);
Runnable t3 =
new
TestAtomicLong(
"王五"
,
3
);
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.shutdown();
}
结果
pool-
1
-thread-
1
: 张三执行了
1
,当前:[
5
,
0
,
2
]
pool-
1
-thread-
2
: 李四执行了
2
,当前:[
5
,
0
,
2
]
pool-
1
-thread-
1
: 王五执行了
3
,当前:[
9
,
0
,
3
]
|
五.原子更新引用
AtomicReference是作用是对"对象"进行原子操作。AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。六.原子更新字段
AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater基于反射的实用工具,可以对指定类的指定 volatile 字段进行原子更新。API非常简单,但是也是有一些约束:(1)字段必须是volatile类型的
(2)字段的描述类型(修饰符public/protected/default/private)是与调用者与操作对象字段的关系一致。也就是说 调用者能够直接操作对象字段,那么就可以反射进行原子操作。但是对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段。
(3)只能是实例变量,不能是类变量,也就是说不能加static关键字。
(4)只能是可修改变量,不能使final变量,因为final的语义就是不可修改。实际上final的语义和volatile是有冲突的,这两个关键字不能同时存在。
(5)对于AtomicIntegerFieldUpdater 和AtomicLongFieldUpdater 只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater 。
后续将继续修补中.