Java потоков: почему возникают несоответствия, если потоки имеют спящий вызов? Без проблем с гонкой во сне проблем почти не существует - PullRequest
0 голосов
/ 01 мая 2020

Несколько потоков атакуют один объект (класса SomeNumber) и вызывают его метод increment(), который увеличивает единственный атрибут в нем (целое число). Это простая программа для образовательных целей. Когда потоки просто вызывают метод increment(), конечный результат почти всегда корректен (одна или две ошибки на 5000 итераций). Однако, если метод Thread.sleep вызывается до вызова метода приращения, даже если он составляет 1 миллисекунду, частота ошибочных конечных результатов резко возрастает.

Что такое метод Thread.sleep, который вызывает так много путаница? Разве условие гонки не должно быть одинаковым с или без вызова sleep?

class Incrementer extends Thread {
  private SomeNumber n;
  public Incrementer (Number n) { this.n=n; }
  public void run() {
    try { Thread.sleep(1); } catch (Exception e) {}
    n.increment();
  }
}

class SomeNumber {
  private int number;
  public void increment() { number++; }
  public int returnNumber() { return number; }
}

public class App {

    private static int wrongCount=0;

    public static void main(String[] args)
    {
        int number;
        final int ITER = 1000;

        for (int i=0; i<ITER; i++)
        {
            // work method does the actual thread thing
            // and here I'm counting the wrong results
            number = work();
            if(number != 10) wrongCount++;
        }

        // printing the number and the percentage of wrong results
        System.out.println("In " + ITER + " passes there are " +
        wrong count + " wrong results (" +
        (float)wrongCount/ITER*100 + "%).");

    }

    private static int work()
    {
        SomeNumber sn = new SomeNumber();

        Thread t01 = new Incrementer(sn);
        Thread t01 = new Incrementer(sn);
        Thread t02 = new Incrementer(sn);
        Thread t03 = new Incrementer(sn);
        Thread t04 = new Incrementer(sn);
        Thread t05 = new Incrementer(sn);
        Thread t06 = new Incrementer(sn);
        Thread t07 = new Incrementer(sn);
        Thread t08 = new Incrementer(sn);
        Thread t09 = new Incrementer(sn);
        Thread t10 = new Incrementer(sn);       

        t01.start();
        t02.start();
        t03.start();
        t04.start();
        t05.start();
        t06.start();
        t07.start();
        t08.start();
        t09.start();
        t10.start();


        try
        {
            t01.join();
            t02.join();
            t03.join();
            t04.join();
            t05.join();
            t06.join();
            t07.join();
            t08.join();
            t09.join();
            t10.join();
        }
        catch (Exception e) {}

        return sn.returnNumber();

    }

}

В основном методе (я добавил его позже, так что теперь он здесь) создаются 10 потоков, запускаются и объединяются (к каждому один и тот же SomeNumber объект пропущен). Все они увеличивают число на единицу. Конечное число должно быть 10. Если нет сна, оно почти всегда есть. Обратите внимание: это не означает, что условия гонки не существуют. Также, если метод приращения вызывается перед сном, результат почти всегда равен 10 (не всегда). Однако, если сон вызывается до приращения, результат сильно варьируется. Почему такая большая разница в несогласованности результатов, когда задействован Thread.sleep (даже короткий, чем 1 мс)?

На моей машине (Windows 10):

sleep 5ms: error rate ~24%
sleep 1ms: error rate ~16%
no sleep: error rate ~0.005%

Это довольно крутой спад.

...