Java多线程复习与巩固(七)--原子性操作

来源:互联网 发布:git ssh eclipse 端口 编辑:程序博客网 时间:2024/05/29 09:08

前面讲线程同步时,我们对多线程出现的问题进行了分析,在那个例子中,问题的根源在于c++c--这两个操作在底层处理的时候被分成了若干步执行。当时我们用的是synchronized关键字来解决这个问题,而从synchronize的实现原理中我们知道synchronized通过monitor监视器来实现线程同步,这种同步方式要求线程等待monitor的拥有者线程释放后,才可能进一步执行。这片文章中我们使用另一种方式来解决前面提出的多线程问题。

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

先贴出代码:

import 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());    }}

程序运行结果:

0

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

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

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

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

java.util.concurrent.atomic

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

原子操作类

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

1. AtomicBooleanAtomicIntegerAtomicLongAtomicReference是对volatile修饰的单一值进行封装。

volatile关键字只能保证多线程读取、写入操作的一致性,但不能保证多线程修改操作的原子性,比如++--之类的操作是不能保证线程安全的。但AtomicIntegerAtomicLong类保证了++--这类操作的原子性,因此这些方法是线程安全的。

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

这里没有提供byteshortfloatdoublechar的包装类,Java官方文档给出的建议是使用已有的AtomicIntegerAtomicLong来自己实现相应的包装类。比如:

  • AtomicInteger来存储byte数据,进行相应的强制转换即可;
  • AtomicInteger来存储float数据,并使用Float.floatToRawIntBits(float)Float.intBitsToFloat(int)方法进行转换;
  • AtomicLong来存储float数据,并使用Double.doubleToRawLongBits(double)Double.longBitsToDouble(long)进行转换。

Demo:

import 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();    }}

2. AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray是对数组类型的值进行原子性操作的封装。

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

3. AtomicIntegerFieldUpdaterAtomicLongFieldUpdaterAtomicReferenceFieldUpdater是对类对象的某个字段进行原子操作。

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

4. AtomicMarkableReferenceAtomicStampedReference是对AtomicReference类的扩展。这两个类的区别在于AtomicMarkableReference使用boolean与引用类型进行关联,你可以使用这个boolean标识引用数据是否被删除;而AtomicStampedReference使用integer于引用类型进行关联,你可以使用这个integer代表引用数据更新的版本数值。

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

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

public class AtomicMarkableReference<V> {    private static class Pair<T> {        final T reference;        final boolean mark;        private Pair(T reference, boolean mark) {            this.reference = reference;            this.mark = mark;        }        static <T> Pair<T> of(T reference, boolean mark) {            return new Pair<T>(reference, mark);        }    }    private volatile Pair<V> pair;    public AtomicMarkableReference(V initialRef, boolean initialMark) {        pair = Pair.of(initialRef, initialMark);    }    ...}public class AtomicStampedReference<V> {    private static class Pair<T> {        final T reference;        final int stamp;        private Pair(T reference, int stamp) {            this.reference = reference;            this.stamp = stamp;        }        static <T> Pair<T> of(T reference, int stamp) {            return new Pair<T>(reference, stamp);        }    }    private volatile Pair<V> pair;    public AtomicStampedReference(V initialRef, int initialStamp) {        pair = Pair.of(initialRef, initialStamp);    }    ...}

在Java1.8中还增加了DoubleAccumulatorDoubleAdderLongAccumulatorLongAdder这四个类用于累积计数。

原创粉丝点击