尽管有锁,Java 多线程中出现意外结果

Unexpected results in Java multi threads despite locks

我正在努力处理一些应该使用线程计算平方和的练习代码。 出于某种原因,尽管使用了锁,但我得到的结果不一致,还确保我锁定的是对象而不是本地项目/变量等。

当我添加日志记录以了解行为时,代码 运行 慢得多但执行得很好。减少到一个线程时也是如此。但是 - 一旦我 运行 多个线程,它就会无法预测。 结果应该是

我的代码:

    package com.yaniv.concurrency;

import java.sql.Timestamp;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class SumSquaresSync implements Runnable {

    ReentrantLock nextLock = new ReentrantLock();
    ReentrantLock sumLock = new ReentrantLock();

    Long sum = new Long(0);
    int min, max;
    Integer next = new Integer(0);
    int threadBatchSize = 10;

    static final boolean LOG = false;

    @Override
    public void run() {
        long localSum = 0;
        int[] batch = new int[threadBatchSize];

        while (next <= max) {
            nextLock.lock();                
            if (this.next <= max) {
                {
                    for (int i = 0; i < threadBatchSize; i++) {
                        batch[i] = ((next + i <= max) ? (next + i) : 0);
                    }
                    next += threadBatchSize;
                }
            }
            nextLock.unlock();


            if (LOG) {
                synchronized (System.out) {
                    System.out.print(Thread.currentThread().getName() + " got batch " + batch.toString() + ": ");
                    for (int i = 0; i < threadBatchSize; i++) {
                        System.out.print(batch[i] + ", ");
                    }
                    System.out.println();
                }
            }

            for (int i : batch) {
                localSum += Math.pow(i, 2);
            }
        }

        sumLock.lock();
        sum += localSum;
        sumLock.unlock();

        if (LOG) {
            safePrintln(Thread.currentThread().getName() + " terminated, localSum = " + localSum);
        }
    }

    private long executeSumSquares(int min, int max, int numberOfThreads, int threadBatchSize) throws Exception {
        this.min = min;
        this.max = max;
        this.next = min;
        this.threadBatchSize = threadBatchSize;
        this.sum = 0L;

        ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);

        for (int i = 0; i < numberOfThreads; i++) {
            if (LOG) {
                System.out.format("Adding thread  %d%n", i);
            }
            executorService.execute(new SumSquaresSyncThread(this));
        }

        executorService.shutdown();
        executorService.awaitTermination(5_000L, TimeUnit.MILLISECONDS);

        return sum;

    }

    public static void main(String[] args) throws Exception {
        SumSquaresSync SumSquaresSync = new SumSquaresSync();
        long total;
        int iteration = 0;
        Timestamp startTime, endTime;

        do {
            iteration++;
            startTime = new Timestamp(System.currentTimeMillis());
            total = SumSquaresSync.executeSumSquares(1, 10000, 1, 5);
            endTime = new Timestamp(System.currentTimeMillis());

            System.out.println("==========================================");
            System.out.format("Total sum: %,8d, elapsed time %d, iteration %d%n", total, (endTime.getTime() - startTime.getTime()), iteration);
            System.out.println("==========================================");

        } while (iteration < 10);  //(total == 333383335000L);


    }

    public void safePrintln(String s) {
        synchronized (System.out) {
            System.out.println(s);
        }
    }

    public void safePrint(String s) {
        synchronized (System.out) {
            System.out.print(s);
        }
    }

}

结果:

Total sum: 347,938,671,335, elapsed time 16, iteration 1
Total sum: 342,283,818,850, elapsed time 10, iteration 2
Total sum: 336,257,779,565, elapsed time 10, iteration 3
Total sum: 336,233,345,285, elapsed time 9, iteration 4
Total sum: 337,663,242,000, elapsed time 8, iteration 5
Total sum: 336,779,784,290, elapsed time 10, iteration 6
Total sum: 335,474,886,225, elapsed time 10, iteration 7
Total sum: 338,825,524,135, elapsed time 8, iteration 8
Total sum: 335,820,751,880, elapsed time 10, iteration 9
Total sum: 335,083,150,300, elapsed time 8, iteration 10

正确的结果应该是333,383,335,000。 谁能告诉我缺少什么?

(从评论中复制)

您的 while (next <= max) 支票不受锁保护。

可能是这个原因,你在锁里面再检查一遍,但是for (int i : batch) { localSum += Math.pow(i, 2); }部分不在里面,所以localSum有时可能会加两次批。

for (int i : batch) 循环移动到 if (this.next <= max) 内。