Новенький, но запутанный вопрос в Java многопоточном программировании - PullRequest
0 голосов
/ 27 мая 2020

Я новичок в Java. У меня возникла проблема при обучении Java многопоточному программированию, и вот код:

public class TestMultiThreads {
    private static int i = 100;
    private static Runnable r = () -> {
        while (i > 0) {
            i--;
            System.out.println(i);
        }
    };

    public static void main(String[] args) {
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();
    }
}

И результат здесь: (не все то же самое, но почти такое)

99
97
96
...
2
1
0
98

Из того, что я узнал, я знаю, что 2 потока вытесняют срезы времени процессора. Однако в этом случае я не могу найти ни одной ситуации, которая могла бы соответствовать выходу.


Обновление

Всем спасибо! С вашей помощью я только что преобразовал файл .class в байт-код Java, и я думаю, что у меня есть предположение. (или ближе к истине)

это часть байт-кода, run() метод в Runnable r

  public void run();
    Code:
       0: invokestatic  #2                  // Method top/littlefogcat/concurrent/TestMultiThreads.access$000:()I
       3: ifle          22
       6: invokestatic  #3                  // Method top/littlefogcat/concurrent/TestMultiThreads.access$010:()I
       9: pop
      10: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      13: invokestatic  #2                  // Method top/littlefogcat/concurrent/TestMultiThreads.access$000:()I
      16: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
      19: goto          0
      22: return

Я не очень понимаю Java байт-код, но могу прочитать некоторые.

Думаю, вот что: возможно, я неправильно понял, как JVM распределяет фрагменты времени ЦП. Я думал, что только когда поток завершает sh выполнение одной строки кода, другой поток имеет возможность вытеснить фрагменты времени ЦП. Возможно это неправильно. Одна строка кода Java может быть преобразована в несколько строк байт-кода. JVM может распределять время ЦП на основе меньшего блока, такого как строка байт-кода или что-то еще. В любом случае, использовать переменную stati c в Java небезопасно. Необходимо использовать synchronized или другие поточно-ориентированные способы.

И спасибо вам, ребята

1 Ответ

2 голосов
/ 27 мая 2020

Загляните внутрь метода println:

    /**
     * Prints an integer and then terminate the line.  This method behaves as
     * though it invokes {@link #print(int)} and then
     * {@link #println()}.
     *
     * @param x  The {@code int} to be printed.
     */
    public void println(int x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

Вы можете увидеть, что значение i копируется в x при вызове println. Поэтому, когда один поток вызывает метод со значением i за раз, он может перестать работать прямо перед synchronized (this) {. Тем временем другой поток может выполнять много отпечатков, но значение x для остановленного потока останется прежним.

Это вызовет странный порядок печати и может даже привести к дублированию отпечатков и пропуску значений, когда оба потоки входят в метод в одно и то же время. Например, оба увеличивают i, а затем вызывают println с ним. Когда i увеличивалось в два раза, то, очевидно, одно значение было пропущено, и оба потока использовали один и тот же i для копирования в x.


Проблема root в том, что i > 0, i-- и println(i) всегда должны быть согласованными. Эти три операции должны быть atomi c. Но в данной программе они не синхронизированы должным образом, поэтому согласованность не обеспечивается.

Вот пример того, как обеспечить согласованность:

    private static int i = 100;
    private static final Object lock = new Object();
    private static Runnable r = () -> {
            while (true) {
                synchronized (lock) {
                    if (i == 0) {
                        break;
                    }
                    i--;
                    System.out.println(i);
                }
            }
    };

    public static void main(String[] args) {
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...