вывод значения общей переменной двух потоков

196
22 декабря 2021, 12:20

Пытаюсь разобраться с многопоточностью в java. Вот простенький код:

    public class ThreadTest {
    private int counter;
    public static void main(String[] args) {
        ThreadTest test = new ThreadTest();
        test.increment();
    }
        public void increment(){
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for(int i=0; i<10; i++){
                        counter++;
                    }
                    //System.out.println(counter);
                }
            });
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for(int i=0; i<10; i++){
                        counter++;
                    }
                    //System.out.println(counter);
                }
            });
            thread1.start();
            thread2.start();
            System.out.println(counter);
        }
}

Я ожидаю увидеть после отработки двух потоков значение общей переменной, которое будет случайным, но максимум 20. Тем не менее в консоль постоянно выводится 0. При этом, если раскомментировать System.out.println в обоих потоках, то выводятся действительно случайные значения (например, 10 и 20, 20 и 20, 14 и 14). Почему последний System.out.println всегда выводит 0 и как сделать так, чтобы выводилась общая переменная после отработки двух потоков?

Answer 1

У вас есть поток и вы создаёте ещё два. При этом System.out.println(counter); в основном потоке выполняется сразу после старта двух других (которые, скорей всего, ещё не успели даже запуститься). Чтобы дождаться выполнения другого потока нужно вызвать метод join().

thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter);

Но это не главная проблема. Основная проблема тут в состоянии гонки (Data Race / Состояние гонки). Это не видно на цикле из 20 итераций, но сделаем до 10000 и уже вместо ожидаемых 20000 выводится разное число (13208, 10865, 13532 для 3 запусков у меня на машине). Состояние гонки происходит, потому что counter++; не атомарная операция (Атомарные и неатомарные операции (java)).

Как это исправить? Обернуть нужный участок в synchronized блок

public class Jclass {
    private int counter;
    final Object mutex = new Object();
    ...
    ...
    public void increment() throws InterruptedException {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    synchronized (mutext) {
                        counter++;
                    }
                }
            }
        };
        Thread thread1 = new Thread(r);
        Thread thread2 = new Thread(r);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(counter);
    }
}

Либо использовать атомарные классы

public class Jclass {
    private final AtomicInteger counter = new AtomicInteger();
    ...
    ...
    public void increment() throws InterruptedException {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    counter.getAndIncrement(); // эквивалент counter++
                    //counter.incrementAndGet(); // эквивалент ++counter
                    //counter.set(x); // эквивалент counter = x
                    // и другие методы
                }
            }
        };
        Thread thread1 = new Thread(r);
        Thread thread2 = new Thread(r);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(counter.get());
    }
}
READ ALSO
Аналог пинг-понга в многопоточности

Аналог пинг-понга в многопоточности

Не могу понять, почему в данном примере не получается работать с 2 потоками

93
Nexus загружает артифакт, но я получаю ошибку, что артифакт нельзя найти в maven-public

Nexus загружает артифакт, но я получаю ошибку, что артифакт нельзя найти в maven-public

У меня есть своя библиотека servicejar которую я подключаю через nexus

208
Spring boot with firebird database null data

Spring boot with firebird database null data

Пробую делать REST сервис на Spring Boot + Firebird 30

199
Неправильный Consumer

Неправильный Consumer

Дано на текущий момент 3 класса и интерфейс

82