AtomicLong не проходит точно между потоками - PullRequest
0 голосов
/ 12 октября 2018

Упражнение относится к пункту 78 «Эффективной Java».А именно, мы создаем два потока, которые постепенно увеличивают общую статическую переменную и распечатывают ее.Цель состоит в том, чтобы произвести единую линию увеличивающихся чисел к консоли.AtomicLong используется, чтобы избежать условий гонки, но есть ошибка, которую я не могу объяснить.А именно, при первом вызове

System.out.println(i.getAndIncrement());

JVM не считывает самое последнее значение переменной.Только на втором звонке это читает. Посмотрите, пожалуйста, вывод консоли с несогласованным выводом, помеченным Может кто-нибудь подсказать мне, как научиться самостоятельно устранять эту ошибку?Не пора ли еще читать спецификации JVM?

package com.util.concurrency.tick;
import java.util.concurrent.atomic.AtomicLong;

public class AtomicIncrementer implements Runnable {

  private String name;
  private static final int MAXI = 1000;
  private static final AtomicLong i = new AtomicLong(-1);

  public AtomicIncrementer(String name){
    this.name = name;
  }

  public void run(){
    while(i.get() < MAXI){
      System.out.println(name+ ". i = "
        +i.getAndIncrement());
    }
    System.out.println(name+" i = "+i.get()); 
  }

  public static void main(String[] args){;
         try {
              Thread t1 = new Thread(new AtomicIncrementer("A"));
              Thread t2 = new Thread(new AtomicIncrementer("B"));
              t1.start();
              t2.start();
            } catch (Exception e) {
            }
        System.out.println("Two incrementers launched");
   }
}

Ответы [ 4 ]

0 голосов
/ 12 октября 2018

У вас есть много условий гонки здесь.Следующий результат событий приведет к результату, который вы видите:

Поток A работает до i=198.Затем происходит переключение контекста, и поток B запускает следующую команду:

System.out.println(name+ ". i = "
        +i.getAndIncrement());

внутри цикла while.Поток B создает строку:

"B. I = 198"

Но затем снова происходит переключение контекста на поток A, прежде чем поток B сможет распечатать эту строку.Таким образом, поток A продолжает выполняться, пока не напечатает

A.i = 204

Затем происходит переключение контекста на поток B, и он возобновляет работу с того места, где остановился ранее, при печати строки:

B.i = 198

По сути, у вас есть условие гонки до получения текущего значения i и его печати. ​​

Другими словами i.getAndIncrement(); является атомарной операцией.Но

System.out.println(name+ ". i = "
        +i.getAndIncrement());

НЕ является атомарной операцией.

У вас есть несколько операций.В псевдокоде:

 1. tempInt = i.getAndIncrement();
 2. tempString1 = name + ". i = ";
 3. tempString2 = convertToString(tempInt);
 4. tempString3 = tempString1 + tempString2;
 5. print tempString3;

Вот почему у вас такой беспорядочный вывод:)

Если вы хотите глубже понять эти понятия, я рекомендую этот онлайн-курс: https://www.udemy.com/java-multithreading-concurrency-performance-optimization/?couponCode=CONCURRENCY

Это действительно дешево, и вы можете закончить его за несколько часов.Но это действительно углубляется в те концепции и предостережения с атомарными классами.Это большие затраты времени.

В вашем случае вы действительно хотите использовать синхронизацию вместо AtomicLong, поскольку у вас есть многозадачный критический раздел.

Надеюсь, это поможет.

0 голосов
/ 12 октября 2018

Это, вероятно, то, что вы пытаетесь сделать :) Проверьте документы Java.Вы хотите синхронизировать между двумя потоками.Обычно каждый поток получает 1 ход для увеличения и передает счетчик другому потоку.

public class AtomicIncrementer implements Runnable {

    private static final int MAXI = 1000;
    private static final SynchronousQueue<Long> LONG_EXCHANGER = new SynchronousQueue<>();

    private final String name;

    private AtomicIncrementer(String name) {
        this.name = name;
    }

    @Override
    public void run() {

        try {

            while (true) {

                Long counter = LONG_EXCHANGER.take();
                if (counter >= MAXI) {
                    LONG_EXCHANGER.put(counter);
                    break;
                }

                System.out.println(name + ". i = " + (counter + 1));
                LONG_EXCHANGER.put(counter + 1);
            }

            Long counter = LONG_EXCHANGER.take();
            System.out.println(name + " final i = " + counter);
            LONG_EXCHANGER.put(counter);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        Thread t1 = new Thread(new AtomicIncrementer("A"));
        Thread t2 = new Thread(new AtomicIncrementer("B"));
        t1.start();
        t2.start();
        System.out.println("Two incrementers launched");

        try {
            LONG_EXCHANGER.put(-1L);
            t1.join();
            System.out.println("T1 ended");
            //this is needed for last thread to end
            LONG_EXCHANGER.take();
            t2.join();
            System.out.println("T2 ended");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
0 голосов
/ 12 октября 2018

Синхронизация не является проблемой, поскольку AtomicLong как раз для этого.Вместо этого общие поля должны быть обновлены из & в локальную память потоков при переключении потоков.Ибо это существует volatile:

private static final volatile AtomicLong i = new AtomicLong(-1);

На самом деле я не совсем уверен!Для long ситуация ясна, но есть только один объект;его длинное поле обновляется.

0 голосов
/ 12 октября 2018

В основном вы не использовали синхронизацию .

, вы можете изменить свой run метод, как показано ниже:

public void run(){

      synchronized(i){
    while(i.get() < MAXI){
      System.out.println(name+ ". i = "
        +i.getAndIncrement());
    }

    System.out.println(name+" i = "+i.get());
      }
  }

Также вы можете обратиться к этому руководствучтобы получить больше информации о синхронизации : https://www.tutorialspoint.com/java/java_thread_synchronization.htm

...