Java并发_5 原子类

原子类

什么是原子类,有什么作用?

java.util.concurrent.atomic中的都是原子类,具有不可分割的性质,即一个操作是不可中断的,即便是多线程的情况下也可以保证。

原子类的作用和锁类似,是为了保证并发情况下线程安全。但原子类相比于锁,有一定的优势:

  1. 粒度更细︰原子变量可以把竞争范围缩小到变量级别,这是我们可以获得的最细粒度的情况了,通常锁的粒度都要大于原子变量的粒度。
  2. 效率更高:通常,使用原子类的效率会比使用锁的效率更高,除了高度竞争的情况。
  3. 使用简单。

6类原子类纵览

原子类分类实现类
Atomic*基本类型原子类Atomiclnteger、AtomicLong、AtomicBoolean
Atomic*Array数组类型原子类AtomiclntegerArray、AtomicLongArray、AtomicReferenceArray、
Atomic*Reference引用类型原子类AtomicReference、AtomicStampedReference、AtomicMarkableReferenceAtomic
Atomic*FieldUpdater升级类型原子类Atomiclntegerfieldupdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
Adder累加器LongAdder、DoubleAdder
Accumulator累加器LongAccumulator、DoubleAccumulator

Atomic*基本类型原子类

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 )

使用范例

如果仅仅是对数值需要保证线程安全的情况,可以使用AtomicInteger类

正常的操作变量是: 1. 读取 2. 进行改变 3. 再写回

public class AtomicIntegerDemo implements Runnable{
    private static final AtomicInteger atomicInt = new AtomicInteger();
    private static volatile int basicInt = 0;

    private void incrementAtomic(){
        atomicInt.getAndIncrement();
    }

    //可以通过加synchronized来解决问题,但是如果方法内容增多,而且本来上锁就是及其耗费资源的操作
    private void incrementBasic(){
        basicInt++;
    }

    public static void main(String[] args) {
        AtomicIntegerDemo r = new AtomicIntegerDemo();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(atomicInt.get());
        System.out.println(basicInt);
    }


    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            incrementAtomic();
            incrementBasic();
        }
    }
}

Atomic*Array数组类型原子类

对整个数组进行加减操作,可以看到这个数组原子类是有效果的。

public class AtomicArrayDemo {
    //验证原子性
    public static void main(String[] args) {
        AtomicIntegerArray atomicIntegerArray = new
                AtomicIntegerArray(1000);
        Incrementer incrementer = new Incrementer(atomicIntegerArray);
        Decrementer decrementer = new Decrementer(atomicIntegerArray);

        Thread[] threadsIncre = new Thread[100];
        Thread[] threadsDecre = new Thread[100];
        for (int i = 0; i < 100; i++) {
            threadsIncre[i] = new Thread(incrementer);
            threadsDecre[i] = new Thread(decrementer);
            threadsIncre[i].start();
            threadsDecre[i].start();
        }

        for (int i = 0; i < 100; i++) {
            try {
                threadsIncre[i].join();
                threadsDecre[i].join();
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        for (int i = 0; i < atomicIntegerArray.length(); i++) {
            if (atomicIntegerArray.get(i) != 0){
                System.out.println("wrong");
            }
        }
        System.out.println("over");

    }
}

class Decrementer implements Runnable {
    private AtomicIntegerArray array;

    public Decrementer(AtomicIntegerArray array) {
        this.array = array;
    }

    @Override
    public void run() {
        for (int i = 0; i < array.length(); i++) {
            array.getAndDecrement(i);
        }

    }
}
class Incrementer implements Runnable {
    private AtomicIntegerArray array;

    public Incrementer(AtomicIntegerArray array) {
        this.array = array;
    }

    @Override
    public void run() {
        for (int i = 0; i < array.length(); i++) {
            array.getAndIncrement(i);
        }

    }
}

比如在做账或者财务管理,经常会有并发的修改,需要原子性且数据量又大,那么就可以将数组转换成 Atomic*Array

源码

这里有一个构造方法,可以转换数组

public AtomicIntegerArray(int[] array) {
    // Visibility guaranteed by final field guarantees
    this.array = array.clone();
}

Atomic*Reference引用类型原子类

compareAndSet(V expect, V update)

把比较和设置合并为一个原子操作,可以原子的改变一个引用。

private AtomicReference<Thread> sign = new AtomicReference<>();
public void lock(){
    Thread cur = Thread.currentThread();
    //通过引用原子类来达到上锁的目的
    while (!sign.compareAndSet(null,cur)){
        System.out.println("失败");
    }
}

把普通变量升级为原子类

用AtomicIntegerFieldUpdater升级普通变量

使用场景

如果需要并发安全可以直接使用原子类,但是

  1. 有些类没有权利修改,就可以使用Updater来达到。
  2. 偶尔需要一个原子get-set操作,比如每天0点的时候才需要一个原子的更新。大多数情况下只需要一个普通类型就可以,那么就不需要额外生成一个原子类,毕竟操作内存会有额外消耗的。

示例

效果和正常原子变量是一样的

public class FieldUpdaterDemo implements Runnable {
    static Candidate normalOne;
    static Candidate updateOne;

    public static AtomicIntegerFieldUpdater<Candidate> scoerUpdater =
            AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");

    public static void main(String[] args) throws InterruptedException {
        normalOne = new Candidate();
        updateOne = new Candidate();
        FieldUpdaterDemo r = new
                FieldUpdaterDemo();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("普通变量:" + normalOne.score);
        System.out.println("升级后的结果" + updateOne.score);

    }


    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            normalOne.score++;
            scoerUpdater.getAndIncrement(updateOne);
        }
    }

    public static class Candidate {
        volatile int score;
    }

}

源码

AtomicIntegerFieldUpdater<T>使用newUpdater方法返回一个构造的一个静态final内部原子对象

@CallerSensitive
public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass,
                                                          String fieldName) {
    return new AtomicIntegerFieldUpdaterImpl<U>
        (tclass, fieldName, Reflection.getCallerClass());
}

而这个内部类中的构造方法中使用该类类对象反射调用了getDeclaredField,即获取该变量,不考虑修饰符。但是注意,private是不能被获取到的。

通过反射操作类的私有(private)成员变量时,需要通过field.setAccessible(true)将字段设置为可以访问的。

field = AccessController.doPrivileged(
    new PrivilegedExceptionAction<Field>() {
        public Field run() throws NoSuchFieldException {
            return tclass.getDeclaredField(fieldName);
        }
    });

注意点

  1. 不支持private变量
  2. 不支持static的变量

Adder累加器(long)

是Java 8引入的,相对是比较新的一个类。

高并发下LongAdderkAtomicLong效率高,不过本质是空间换时间。

竞争激烈的时候,LongAdder把不同线程对应到不同的Cell上进行修改,降低了冲突的概率,是多段锁的理念,比如1.7的ConcurrentHashMap使用16段segment,提高了并发性。

演示

这里演示多线程情况下AtomicLong的性能,有16个线程对同一个AtomicLong累加。

  • AtomicLong构造需要初始值,LongAdder没有
  • AtomicLong是incrementAndGet加一并返回,LongAdder是increment方法加1L没有返回值
  • AtomicLong取值是get,LongAdder取值是sum方法
public class AdderTest {
    //演示longadder比atomiclong性能好
    public static void main(String[] args) {
        //AtomicLong counter = new AtomicLong(0);
        LongAdder counter = new LongAdder();

        ExecutorService service = Executors
                .newFixedThreadPool(20);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            service.submit(new Task(counter));
        }

        //这里通过判断是否终止来打印结果,shutdown可详见线程池章节
        service.shutdown();
        while (!service.isTerminated()) {
        }

        //counter.get()
        System.out.println(counter.sum());
        System.out.println(System.currentTimeMillis()-start);
    }

    private static class Task implements Runnable {
        private LongAdder counter;

        public Task(LongAdder counter) {
            this.counter = counter;
        }

        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                //counter.incrementAndGet
                counter.increment();
                //就是add(1)
            }
        }
    }
}

原因分析

这里可以去学习以下缓存一致性,volatile原理

由于竞争很激烈,每一次加法,都要flush和refresh,导致很耗费资源。

在内部,这个LongAdder的实现原理和刚才的AtomicLong是有不同的,刚才的AtomicLong的实现原理是,每一次加法都需要做同步,所以在高并发的时候会导致冲突比较多,也就降低了效率。

而jdk8新增的LongAdder,每个线程会有自己的一个计数器,仅用来在自己线程内计数,这样一来就不会和其他线程的计数器干扰。

第一个线程的计数器数值,ctr’,为1的时候,可能线程2的计数器ctr’’的数值已经是3了,他们之间并不存在竞争关系,所以在加和的过程中,根本不需要同步机制,也不需要刚才的flush和refresh。这里也没有一个公共的counter来给所有线程统一计数。

LongAdder带来的改进

使用空间换时间

  • LongAdder引入了分段累加的概念,内部有一个base变量和一个Cell数组共同参与计数∶
  • base变量:竞争不激烈,直接累加到该变量上
  • Cell[]数组∶竞争激烈,通过hash值将各个线程分散累加到自己的槽Cell[i]中,每个cell都是独立的计数器
sum源码

这里看到有一个小缺陷,因为没有上锁,所以在遍历还没有结束之前,遍历过的数组元素的变动无法体现在结果中

public long sum() {
    Cell[] as = cells; Cell a;
    long sum = base;
    //cell数组不为空,则遍历累加
    if (as != null) {
        for (int i = 0; i < as.length; ++i) {
            if ((a = as[i]) != null)
                sum += a.value;
        }
    }
    //cell为空直接返回只取了base的sum,不为空则返回base加上遍历cell数组的值
    return sum;
}

应用场景的区分

  • 在低争用下,AtomicLong和LongAdder这两个类具有相似的特征。但是在竞争激烈的情况下,LongAdder的预期吞吐量要高得多,但要消耗更多的空间。
  • LongAdder适合的场景是统计求和计数的场景,而且LongAdder基本只有add方法,而AtomicLong还具有CAS方法,比如compareAndSet(long expect, long update)

Accumulator累加器

Accumulator和Adder非常相似,Accumulator就是一个更通用版本的Adder。

public static void main(String[] args) {
    LongAccumulator accumulator = new LongAccumulator((x, y) -> x + y, 0);
    ExecutorService executor = Executors.newFixedThreadPool(6);
    accumulator.accumulate(1);
    System.out.println(accumulator.getThenReset());
    IntStream.range(1,10).forEach(i ->executor.submit(()->accumulator.accumulate(i)));

    executor.shutdown();
    while (!executor.isTerminated()) {
    }

    System.out.println(accumulator.getThenReset());
}

在构造一个Accumulator的时候可以指定任意的二元操作。比如Math.max(a,b),各种加减乘除。

源码

其中LongBinaryOperator是一个函数式接口,那么我们就可以使用函数调用或者lambda表达式来简化代码。

public LongAccumulator(LongBinaryOperator accumulatorFunction,
                       long identity) {
    this.function = accumulatorFunction;
    base = this.identity = identity;
}

使用场景

for (int i = 0; i < 1000; i++) {
    x += y;
}

使用线程池可以将这种操作并行运算,但是要注意适合于操作无关乎计算顺序的任务场景

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值