Страницы

Поиск по вопросам

четверг, 9 января 2020 г.

Многопоточность и синхронизация методов в Java

#java #synchronized


Не могу понять, почему данный код не работает корректно.

import java.util.concurrent.atomic.AtomicInteger;

public class Test1 extends Thread {
    public static AtomicInteger counter = new AtomicInteger(0);
    synchronized void doWork1() {

        counter.incrementAndGet();
        counter.incrementAndGet();

        System.out.println("Thread+++" + " - " + Test1.counter);
        try {
            Thread.currentThread().sleep(10);
        } catch (InterruptedException e) {

        }

    }

    synchronized void doWork2() {
        counter.decrementAndGet();
        counter.decrementAndGet();
        System.out.println("Thread---" + " - " + Test1.counter);
        try {
            Thread.currentThread().sleep(10);
        } catch (InterruptedException e) {

        }
    }

    public void run() {
        for (int i = 0; i < 100; i++) {
            doWork1();
            doWork2();
        }
    }

    public static void main(String[] args) {
        Test1 test1 = new Test1();
        test1.start();
        Test1 test2 = new Test1();
        test2.start();
    }
}

    


Ответы

Ответ 1



При указании ключевого слова synchronized для методов в качестве монитора, который захватывается потоками, используется соответствующий экземпляр класса. Так как у вас два разных экземпляра класса Test1 - потоки в принципе работают независимо друг от друга. Для того, чтобы решить эту проблему, необходимо использовать общий монитор для обоих потоков, например, отдельный объект: private final static Object lock = new Object(); void doWork1() { synchronized(lock) { ... } } void doWork2() { synchronized(lock) { ... } } Таким образом, потоки уже будут ждать освобождения монитора перед выполнением кода внутри блока synchronized.

Ответ 2



Как уже было отмечено выше, синхронизация в случае нестатических методов происходит по объекту, вызывающему этот метод. В данном случае есть два разных экземпляра класса Test1, локи захватываются на разные объекты. Решить проблему можно несколькими путями: либо захватить лок на весь класс: void doWork1() { synchronized(Test1.class) { ... } } либо захватить лок на один и тот же объект( в данном случае можно на counter) void doWork1() { synchronized(counter) { ... } } Я бы предпочел второй вариант.

Ответ 3



incrementAndGet - да, атомарна, но два таких оператора написанных друг за другом не являются таковыми Вот смотри, например, один поток вошел в doWork1 и выполнил операцию counter.incrementAndGet() и в этот момент управление перешло к другому потоку который начинает выполнять doWork2 и выполняет его до конца. на его выходе получишь нечетное число.

Комментариев нет:

Отправить комментарий