Неожиданные результаты в Java многопоточности несмотря на блокировки - PullRequest
1 голос
/ 09 марта 2020

Я борюсь с некоторым кодом упражнения, который должен вычислять сумму квадратов, используя потоки. По какой-то причине я получаю противоречивые результаты, несмотря на использование блокировок, а также убедился, что я блокирую объекты, а не локальные элементы / переменные и т. Д. c.

Когда я добавляю логи для понимания поведения, код запускается намного медленнее и работает просто отлично. То же самое происходит при сокращении до одного потока. Но - как только я запускаю несколько потоков, это действует непредсказуемо. Результат должен быть

Мой код:

    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. Кто-нибудь может сказать, что мне не хватает?

1 Ответ

0 голосов
/ 09 марта 2020

(Скопировано из комментария)

Ваша проверка while (next <= max) не защищена блокировкой.

Вы проверяете ее снова внутри блокировки, вероятно, по этой причине, но часть for (int i : batch) { localSum += Math.pow(i, 2); } не находится внутри внутреннего состояния, поэтому localSum может иногда добавлять партию дважды.

Переместить for (int i : batch) l oop внутрь if (this.next <= max).

...