java多线程变量的原子性_Java多线程复习与巩固(八)--原子性操作与原子变量...

前面讲线程同步时,我们对多线程容易出现的问题进行了分析,在那个例子中,问题的根源在于c++和c--这两个操作在底层处理的时候被分成了若干步执行。当时我们用的是synchronized关键字来解决这个问题,而从synchronize的实现原理中我们知道synchronized通过monitor监视器来实现线程同步,这种同步方式要求线程等待monitor的拥有者线程释放后,才可能进一步执行,而线程等待可能会导致**线程上下文的切换(Context Switch)**,线程上下文的切换会带来极大的开销:保存和恢复线程当前的执行状态(如程序计数器,线程执行栈等)。这片文章中我们使用另一种方式来解决前面提出的多线程问题。

使用原子操作来解决多线程的问题

先贴出代码:

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

47import java.util.concurrent.atomic.AtomicInteger;

public class ThreadCommunicate{

static class Counter{

private AtomicInteger c = new AtomicInteger(0);

public void increment(){

c.getAndIncrement();

}

public void decrement(){

c.getAndDecrement();

}

public int value(){

return c.get();

}

}

static class IncrementTask implements Runnable{

public void run(){

for (int i = 0; i < 10000; i++) {

counter.increment();

}

}

}

static class DecrementTask implements Runnable{

public void run(){

for (int i = 0; i < 10000; i++) {

counter.decrement();

}

}

}

private static Counter counter = new Counter();

public static void main(String[] args) throws InterruptedException{

Thread i = new Thread(new IncrementTask());

Thread d = new Thread(new DecrementTask());

i.start();

d.start();

i.join();

d.join();

System.out.println(counter.value());

}

}

程序运行结果:

10

上面代码中使用了java.util.concurrent.atomic包中的一个类**AtomicInteger**,使用的是类中的getAndIncrement和getAndDecrement方法,这两个方法类似于之前例子中的c++,c--操作。

AtomicInteger是对int类型的封装,**AtomicInteger类中的方法能保证对内存中的int值的操作都是原子性的**,换句话说就能保证一个线程在对int操作的过程中不会被另一个线程打断,从而使得两个线程不会发生前面文章中出现的指令交叉执行的现象。

对于单处理机CPU来说,原子操作指的是一个不会被“线程调度机制”打断的操作,这种操作一旦开始,就一直占用CPU直到操作结束,中间不会有任何上下文切换(context switch,切换到另外的进程或线程)。

对于多处理机CPU来说,原子操作不仅仅具有前面的那些性质,还应包括“在一个处理机上的操作不会受其他处理机的影响”这一特性,比如说一个处理机修改内存的时候另一个处理机不能修改内存。

java.util.concurrent.atomic包

像AtomicInteger这样的类还有很多,它们都在java.util.concurrent.atomic包中,这些类都是无锁的、线程安全的。

8218843e2b6eff230d5acee12b8c8902.png

从功能上来说,上面这些类主要分为以下几种:

1. 单一值原子性封装

AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference是对volatile修饰的单一值进行封装。

由于**volatile关键字只能保证多线程读取(get)、写入(set)操作的一致性,但不能保证多线程修改操作(++,–等操作)的原子性**。但AtomicInteger和AtomicLong类内部使用CAS操作保证了getAndIncrement(i++),getAndDecrement(i–),incrementAndGet(++i),decrementAndGet(–i)等这类操作的原子性。

特别地,AtomicBoolean底层使用int存储,用1表示true,用0表示false,因为在Java中boolean类型的字节长度是不确定的,单个的boolean编译时会被映射为int类型,boolean数组编译时才会被映射为byte类型的数组。用1表示true,用0表示false。

JDK没有提供byte、short、float、double、char的包装类,Java官方文档给出的建议是使用已有的AtomicInteger和AtomicLong来自己实现相应的包装类。比如:

用AtomicInteger来存储byte数据,进行相应的强制转换即可;

用AtomicInteger来存储float数据,并使用Float.floatToRawIntBits(float)和Float.intBitsToFloat(int)方法进行转换;

用AtomicLong来存储double数据,并使用Double.doubleToRawLongBits(double)和Double.longBitsToDouble(long)进行转换。

Demo:

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

47

48

49

50

51

52

53

54

55

56

57import java.util.concurrent.atomic.AtomicInteger;

public class AtomicFloat extends Number{

private int float2Int(float value){

return Float.floatToRawIntBits(value);

}

private float int2Float(int value){

return Float.intBitsToFloat(value);

}

private AtomicInteger bits;

public AtomicFloat(){

this(0f);

}

public AtomicFloat(float initialValue){

bits = new AtomicInteger(float2Int(initialValue));

}

public final boolean compareAndSet(float expect, float update){

return bits.compareAndSet(float2Int(expect), float2Int(update));

}

public final void set(float newValue){

bits.set(float2Int(newValue));

}

public final float get(){

return int2Float(bits.get());

}

public float floatValue(){

return get();

}

public final float getAndSet(float newValue){

return int2Float(bits.getAndSet(float2Int(newValue)));

}

public final boolean weakCompareAndSet(float expect, float update){

return bits.weakCompareAndSet(float2Int(expect), float2Int(update));

}

public double doubleValue(){

return (double) floatValue();

}

public int intValue(){

return (int) get();

}

public long longValue(){

return (long) get();

}

}

Guava的com.google.common.util.concurrent.AtomicDouble包有这些扩展类的实现,

如AtomicDouble、AtomicDoubleArray

2. 数组值原子性封装

AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray是对数组类型的值进行原子性操作的封装。

这三个类在方法中多传入一个索引作为参数来访问数组中的元素,AtomicIntegerArray使用int[]存储,AtomicLongArray使用long[]存储,AtomicReferenceArray使用T[]泛型数组存储。

3. 对象字段原子性封装

AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater是对类对象的某个字段进行原子操作。

这三个类都是抽象类,但是它们都提供了一个工厂方法newUpdater(Class tclass, String fieldName)来创建内部实现类的实例。

这三个类主要用在已经封装好的类,我们无法对这个类的代码进行修改,但是却要保证里面某些字段的操作是原子性的。

4. 对象标志原子性封装

AtomicMarkableReference、AtomicStampedReference是对AtomicReference类的扩展。

这两个类的区别在于AtomicMarkableReference使用boolean与引用类型的值进行关联,这个布尔值用来标识这个引用对象是否被;而AtomicStampedReference使用integer与引用类型的值进行关联,你可以使用这个integer代表引用数据更新的版本数值。

下一篇文章讲CAS操作的ABA问题时会提到这两个类的用处

下面的代码是JDK1.8中AtomicMarkableReference、AtomicStampedReference的部分代码(1.8之前实现有所不同):

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

37public class AtomicMarkableReference{

private static class Pair{

final T reference;

final boolean mark;

private Pair(T reference, boolean mark){

this.reference = reference;

this.mark = mark;

}

static Pair of(T reference, boolean mark){

return new Pair(reference, mark);

}

}

private volatile Pair pair;

public AtomicMarkableReference(V initialRef, boolean initialMark){

pair = Pair.of(initialRef, initialMark);

}

...

}

public class AtomicStampedReference{

private static class Pair{

final T reference;

final int stamp;

private Pair(T reference, int stamp){

this.reference = reference;

this.stamp = stamp;

}

static Pair of(T reference, int stamp){

return new Pair(reference, stamp);

}

}

private volatile Pair pair;

public AtomicStampedReference(V initialRef, int initialStamp){

pair = Pair.of(initialRef, initialStamp);

}

...

}

在Java1.8中还增加了DoubleAccumulator、DoubleAdder、LongAccumulator、LongAdder这四个类用于并发累积计数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值