查看原文
其他

【并发技术09】原子性操作类的使用

倪升武 武哥聊编程 2022-08-24


在 java5 以后,我们接触到了线程原子性操作,也就是在修改时我们只需要保证它的那个瞬间是安全的即可,经过相应的包装后可以再处理对象的并发修改,本文总结一下 Atomic 系列的类的使用方法,其中包含:

1. 基本类型的使用

首先看一下 AtomicInteger 的使用,AtomicInteger 主要是针对整数的修改的,看一下示例代码:

  1. public class AtomicIntegerDemo {

       

  2.    public final static AtomicInteger TEST_INTEGER = new AtomicInteger(1);

  3.    public static void main(String []args) {

  4.         for(int i = 0 ; i < 10 ; i++) { //开启10个线程

  5.             new Thread() {

  6.                 public void run() {

  7.                     try {

  8.                        Thread.sleep(1000);

  9.                    } catch (InterruptedException e) {

  10.                        e.printStackTrace();

  11.                    }

  12.                    int now = TEST_INTEGER.incrementAndGet(); //自增

  13.                    System.out.println(Thread.currentThread().getName() + " get value : " + now);

  14.                 }

  15.             }.start();

  16.         }

  17.    }

  18. }

来看一下结果:

Thread-3 get value : 4
Thread-7 get value : 5
Thread-9 get value : 9
Thread-4 get value : 6
Thread-0 get value : 3
Thread-1 get value : 8
Thread-5 get value : 11
Thread-8 get value : 7
Thread-2 get value : 10
Thread-6 get value : 2

可以看出,10 个线程之间是线程安全的,并没有冲突。也就是说,我们使用原子性操作类去操作基本类型 int 就可以解决线程安全问题,一个线程在操作的时候,会对其它线程进行排斥,不用我们手动去使用 synchronized 实现互斥操作了。AtomicLong 和 AtomicBoolean 类似,就不举例子了。

2. 数组类型的使用

下面要开始说 Atomic 的数组用法,Atomic 的数组要求不允许修改长度等,不像集合类那么丰富的操作,不过它可以让数组上每个元素的操作绝对安全的,也就是它细化的力度还是到数组上的元素,做了二次包装,虽然是数组类型的,但是最后还是操作数组中存的数,所以会了上面的基本类型的话,数组类型也很好理解。这里主要看一下 AtomicIntegerArray 的使用,其它的类似。

  1. public class AtomicIntegerArrayTest {

  2.    

  3.    private final static AtomicIntegerArray ATOMIC_INTEGER_ARRAY = new AtomicIntegerArray(new int[]{1,2,3,4,5,6,7,8,9,10});

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

  5.        Thread []threads = new Thread[10];

  6.        for(int i = 0 ; i < 10 ; i++) {

  7.            final int index = i;

  8.            final int threadNum = i;

  9.            threads[i] = new Thread() {

  10.                public void run() {

  11.                    int result = ATOMIC_INTEGER_ARRAY.addAndGet(index, index + 1);

  12.                    System.out.println("线程编号为:" + threadNum + " , 对应的原始值为:" + (index + 1) + ",增加后的结果为:" + result);

  13.                }

  14.            };

  15.            threads[i].start();

  16.        }

  17.        for(Thread thread : threads) {

  18.            thread.join();

  19.        }

  20.        System.out.println("=========================>\n执行已经完成,结果列表:");

  21.        for(int i = 0 ; i < ATOMIC_INTEGER_ARRAY.length() ; i++) {

  22.            System.out.println(ATOMIC_INTEGER_ARRAY.get(i));

  23.        }

  24.    }

  25. }

运行结果是给每个数组元素加上相同的值,它们之间互不影响。

3. 作为类属性的使用

当某个数据类型是某个类中的一个属性的时候,然后我们要操作该数据,就需要使用属性原子修改器了,这里还是以 Integer 为例,即:AtomicIntegerFieldUpdater。示例代码如下:

  1. public class AtomicIntegerFieldUpdaterTest {    

  2.    static class A {  

  3.        volatile int intValue = 100;  

  4.    }        

  5.    

  6.    public final static AtomicIntegerFieldUpdater<A> ATOMIC_INTEGER_UPDATER = AtomicIntegerFieldUpdater.newUpdater(A.class, "intValue");        

  7.    public static void main(String []args) {  

  8.        final A a = new A();  

  9.        for(int i = 0 ; i < 10 ; i++) {  

  10.            new Thread() {  

  11.                public void run() {  

  12.                    if(ATOMIC_INTEGER_UPDATER.compareAndSet(a, 100, 120)) {  

  13.                        System.out.println(Thread.currentThread().getName() + " 对对应的值做了修改!");  

  14.                    }  

  15.                }  

  16.            }.start();  

  17.        }  

  18.    }  

  19. }  

可以看到,这里需要将类和类属性传进去才行,传进去后其实跟前面操作 Integer 没什么不同了,本质都一样的,运行一下,结果只有一个线程能对其进行修改。
线程的原子性操作类的使用就简单总结到这,其他的操作类原理都相似,可以参考 JDK 的文档,可以很容易写出相应的测试代码。

如果觉得对您有帮助,请转发给更多人吧~

关注“程序员私房菜”,学习更多技术干货,领取更多免费资源

↓↓↓


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存