使用原子数组

当您实现一个有一个或多个对象被数个线程共享的并发应用, 您必须使用一同步机制(如锁或synchronized关键字)保护对它们的属性的访问以避免数据不一致的错误。

这些机制有以下问题:

  • 死锁:此情况发生在一个线程被阻塞等待一个被其他线程锁定且将永不释放的锁。此情况阻塞了程序,程序将用不停止。
  • 如果只有一个线程访问此共享对象,也必须执行获取、释放锁的代码

为了在此情况提供更好的性能,开发了 比较并交换 操作。此操作通过以下3步改变变量值:

  1. 获得变量的值,这是变量的旧值
  2. 在一个临时变量中改变此值,这是变量的新值
  3. 比较旧值和变量值
    旧值和变量值不一定相等,因为另一个线程可能改变了变量值
    • 如果相等,用新值替换旧值
    • 如果不相等,重新从第一步开始

用此机制,不需使用任何同步机制,这样避免了死锁,同时获得了更好的性能。

Java在 原子变量 中实现了此机制。这些变量提供的 compareAdnSet() 方法是 比较并交换 操作的实现和其他方法的基础。

Java 也引入了 原子数组 ,为整型和长整型数组提供原子操作。

任务

学习如何使用 AtomicIntegerArray 类来操作原子数组。

实现

本节的示例代码在 com.elanzone.books.noteeg.chpt6.sect09 package中

  • Runnable 实现类 : Incrementer

    • 属性 vector : AtomicIntegerArray 对象
    • 在构造函数中将属性初始化为参数值
        private AtomicIntegerArray vector;
    
        public Incrementer(AtomicIntegerArray vector) {
            this.vector = vector;
        }
    
    • run() 方法:用 getAndIncrement() 方法增加数组中所有元素的值
        @Override
        public void run() {
            for (int i = 0; i < vector.length(); i++) {
                vector.getAndIncrement(i);
            }
        }
    
  • Runnable 实现类 : Decrementer

    • 属性 vector : AtomicIntegerArray 对象
    • 在构造函数中将属性初始化为参数值
        private AtomicIntegerArray vector;
    
        public Decrementer(AtomicIntegerArray vector) {
            this.vector = vector;
        }
    
    • run() 方法:用 getAndDecrement() 方法减少数组中所有元素的值
        @Override
        public void run() {
            for (int i = 0; i < vector.length(); i++) {
                vector.getAndDecrement(i);
            }
        }
    
  • 控制类 : Main

    1. 创建一个有 1000 个元素的 AtomicIntegerArray 对象 vector
    2. 创建一个使用 vector 的 Incrementer 任务和 Decrementer 任务及各自对应的100个线程
    3. 启动这200个线程,并等待它们结束
    4. 使用 get() 方法获得原子数组的元素值,输出不为0的元素到终端,以确定计算结果正确
    5. 输出结束信息
            final int THREADS = 100;
            AtomicIntegerArray vector = new AtomicIntegerArray(1000);
    
            Incrementer incrementer = new Incrementer(vector);
            Decrementer decrementer = new Decrementer(vector);
    
            Thread threadIncrementer[] = new Thread[THREADS];
            Thread threadDecrementer[] = new Thread[THREADS];
            for (int i = 0; i < THREADS; i++) {
                threadIncrementer[i] = new Thread(incrementer);
                threadDecrementer[i] = new Thread(decrementer);
            }
    
            for (int i = 0; i < THREADS; i++) {
                threadIncrementer[i].start();
                threadDecrementer[i].start();
            }
    
            for (int i = 0; i < THREADS; i++) {
                try {
                    threadIncrementer[i].join();
                    threadDecrementer[i].join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            for (int i = 0; i < vector.length(); i++) {
                if (vector.get(i) != 0) {
                    System.out.println("Vector[" + i + "] : " + vector.get(i));
                }
            }
            System.out.println("Main: End of the example");
    

讲解

本例中实现了2个操作 AtomicIntegerArray 对象的不同的任务:

  • Incrementer 任务: 使用 getAndIncrement() 方法增加数组的所有元素的值
  • Decrementer 任务: 使用 getAdnDecrement() 方法减少数组的所有元素的值

在 Main 类中,创建了一个有 1000 个元素的 AtomicIntegerArray,然后执行了100个Incrementer和100个Decrementer任务。 这些任务结束后,如果没有不一致的错误,数组的所有元素值应该为0. 执行此程序,看看程序有没有输出不为0的元素信息到终端。

了解更多

目前 Java 只提供了另一个原子数组类 AtomicLongArray,它提供了和 AtomicIntegerArray 类一样的方法。

这些类提供的其他一些有趣的方法:

  • get(int i) : 获取数组中参数指定位置的元素值
  • set(int I, int newValue): 设置数组中参数指定位置的元素值