原子类

前面我们介绍过并发编程三大问题:操作原子性、数据可见性、操作有序性,解决操作原子性问题的基本方法是线程同步。不过Java中,如果对于类似i += 1这类操作,其实除了使用线程同步我们还有更好的办法。JDK提供了一系列原子类,我们可以直接使用这些原子类进行原子操作,此外还提供了累加器,可以性能更好的方式实现累加操作。

原子类相比线程同步,具有以下优势:

  1. 粒度更细:原子变量可以把竞争范围缩小到变量级别,比锁粒度更小
  2. 性能更好:JDK的原子类底层使用了CAS机制,相比单纯的使用同步代码块或是互斥锁有更好的并发性能

基本类型原子类

JDK提供了一组基本类型原子类,常用的包括AtomicLongAtomicIntegerAtomicBoolean等,下面我们以AtomicInteger为例进行介绍。

package com.gacfox.demo;

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

public class Main {
    private static final Random random = new Random();
    private static final AtomicInteger atomicInteger = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[100];

        // 创建100个线程对atomicInteger进行操作
        for (int i = 0; i < 100; i++) {
            threads[i] = new Thread(() -> {
                try {
                    // 为了让并发运行效果更明显,这里让线程随机睡一会
                    Thread.sleep(random.nextInt(100));
                    atomicInteger.getAndIncrement();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
            threads[i].start();
        }

        // 等待所有线程执行完
        for (int i = 0; i < 100; i++) {
            threads[i].join();
        }

        // 输出atomicInteger值
        System.out.println(atomicInteger.get());
    }
}

上面代码我们创建了一个AtomicInteger对象,然后创建了100个线程对其进行加1的操作,最终我们可以看到atomicInteger的值为100,这是因为atomicInteger.getAndIncrement()是一个原子操作。但如果上面代码将AtomicInteger改为普通的int,那结果就不好说了,我们得到的结果将是随机的。

除了基本类型的原子类,JDK还提供了数组、引用等原子类,具体可以参考文档,这里就不多介绍了。

累加器

对于累加操作,我们可以直接使用累加器代替原子类加n的写法,累加器底层进行了优化,对累加操作比原子类直接加n性能更好,下面例子以LongAdder为例进行介绍。

package com.gacfox.demo;

import java.util.Random;
import java.util.concurrent.atomic.LongAdder;

public class Main {
    private static final Random random = new Random();
    private static final LongAdder longAdder = new LongAdder();

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[100];

        // 创建100个线程对longAdder进行操作
        for (int i = 0; i < 100; i++) {
            threads[i] = new Thread(() -> {
                try {
                    // 为了让并发运行效果更明显,这里让线程随机睡一会
                    Thread.sleep(random.nextInt(100));
                    longAdder.increment();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
            threads[i].start();
        }

        // 等待所有线程执行完
        for (int i = 0; i < 100; i++) {
            threads[i].join();
        }

        // 输出longAdder值
        System.out.println(longAdder.longValue());
    }
}

代码和之前使用原子类差不多,这里我们调用longAdder.increment()这个原子方法,最终输出结果为100。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap