【问题标题】:Java AtomicInteger not equal to a million after a million iterations (minimal example included)一百万次迭代后Java AtomicInteger不等于一百万(包括最小示例)
【发布时间】:2021-02-25 02:47:31
【问题描述】:

我正在编写一个程序,它计算数组中 0 到 100 万(不包括)之间整数的平方。我最多使用 8 个线程(含)。
为了索引数组,我使用原子整数。
无论线程数如何,我都希望 run 方法中的 for 循环体执行一百万次。
为了计算它执行了多少次,我使用了另一个原子整数。

   static AtomicInteger cnt = new AtomicInteger(0);
    static AtomicInteger ii = new AtomicInteger(0);
    static long[] arr = new long[1_000_000];

    public static class Worker extends Thread {

        public static void main(String[] args) throws IOException, InterruptedException {
            int maxThreads = Runtime.getRuntime().availableProcessors() * 2;
            for (int n = 1; n <= maxThreads; n++) {
                int numThreads = n;
                Thread[] threads = new Thread[numThreads];
                ii = new AtomicInteger(0);
                for (int i = 0; i < threads.length; i++) {
                    threads[i] = new Worker();
                    threads[i].start();
                }
                for (Thread t : threads) {
                    t.join();
                }
                System.out.printf("%d threads, cnt is %d\n" , threads.length, cnt.get());
                cnt.set(0);
            }
        }

        @Override
        public void run() {
            for (int i = ii.get(); i < arr.length; i = ii.getAndIncrement()) {
                arr[i] = (long)i*i;
                cnt.getAndIncrement();
            }
        }
    }

预期的执行结果是:

1 threads, cnt is 1000000
2 threads, cnt is 1000000
3 threads, cnt is 1000000
4 threads, cnt is 1000000
5 threads, cnt is 1000000
6 threads, cnt is 1000000
7 threads, cnt is 1000000
8 threads, cnt is 1000000

但是在运行时我得到以下信息:

1 threads, cnt is 1000001
2 threads, cnt is 1000002
3 threads, cnt is 1000003
4 threads, cnt is 1000002
5 threads, cnt is 1000003
6 threads, cnt is 1000002
7 threads, cnt is 1000002
8 threads, cnt is 1000005

你能帮我调试一下吗?

【问题讨论】:

    标签: java multithreading concurrency atomic atomicinteger


    【解决方案1】:

    有一些小问题,例如运行之间的ii = new AtomicInteger(0) 分配或抛出IOException 的声明永远不会发生。虽然分配给ii 在这里没有影响,因为它发生在没有线程访问ii 的点上,它可能会分散注意力,因为它偏离了多线程代码的既定代码模式。您应该像在两次运行之间重置 cnt 一样重置 ii

    实际问题是循环的起点:

    for (int i = ii.get(); i < arr.length; i = ii.getAndIncrement()) {
    

    您正在使用get 读取而不增加值,因此多个线程可能会读取相同的值,从而导致随后的arr[i] = (long)i*i; 发生数据竞争(此处未注意到,但当然应该避免)并执行由于cnt 更新,您注意到的迭代次数超出了必要的次数。您应该像后续迭代一样使用getAndIncrement() 作为初始索引,以确保每个线程访问不同的数组索引。

    static final AtomicInteger cnt = new AtomicInteger(0);
    static final AtomicInteger ii = new AtomicInteger(0);
    static final long[] arr = new long[1_000_000];
    
    public static class Worker extends Thread {
    
        public static void main(String[] args) throws InterruptedException {
            int maxThreads = Runtime.getRuntime().availableProcessors() * 2;
    
            for (int numThreads = 1; numThreads <= maxThreads; numThreads++) {
                Thread[] threads = new Thread[numThreads];
                for (int i = 0; i < threads.length; i++) {
                    threads[i] = new Worker();
                    threads[i].start();
                }
                for (Thread t : threads) {
                    t.join();
                }
                System.out.printf("Used %d threads, cnt is %d\n" , numThreads, cnt.get());
                cnt.set(0);
                ii.set(0);
            }
        }
    
        @Override
        public void run() {
            for(int i = ii.getAndIncrement(); i < arr.length; i = ii.getAndIncrement()) {
                arr[i] = (long)i*i;
                cnt.getAndIncrement();
            }
        }
    }
    
    Used 1 threads, cnt is 1000000
    Used 2 threads, cnt is 1000000
    Used 3 threads, cnt is 1000000
    Used 4 threads, cnt is 1000000
    Used 5 threads, cnt is 1000000
    Used 6 threads, cnt is 1000000
    Used 7 threads, cnt is 1000000
    Used 8 threads, cnt is 1000000
    Used 9 threads, cnt is 1000000
    Used 10 threads, cnt is 1000000
    Used 11 threads, cnt is 1000000
    Used 12 threads, cnt is 1000000
    Used 13 threads, cnt is 1000000
    Used 14 threads, cnt is 1000000
    Used 15 threads, cnt is 1000000
    Used 16 threads, cnt is 1000000
    

    请注意,复杂的并行处理框架不使用此类原子索引更新,而是在启动线程之前根据预期的目标并行度将范围分成大小相等的子范围,因此每个线程都可以使用普通的局部索引变量。

    使用 Arrays.parallelSetAll(arr, i -&gt; (long)i*i); 或 Stream API 时,您可以免费获得。

    【讨论】:

      【解决方案2】:

      你为什么要这样做:

      ii = new AtomicInteger(0);

      在你的主循环中?对于整个程序,您有 1 个名为 ii 的变量,但它被分配了一个值的轮播,同时在其间启动线程,这意味着每个线程都有您创建的任意一个 (cores*2) 线程,因此您的应用运行的确切方式取决于月相。

      只需删除该行。

      还请注意,您循环了n 'available processor *2' 次,但随后循环内循环了一次 n 再次。这肯定不是你的本意。如果是的话,你的变量名是在撒谎。

      【讨论】:

      • 请查看预期输出。
      • 是的,我看了一下,然后写了这个答案。
      猜你喜欢
      • 2013-04-16
      • 1970-01-01
      • 1970-01-01
      • 2014-06-27
      • 1970-01-01
      • 2011-02-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多