Проблема с синхронизацией потоков в Java - PullRequest
1 голос
/ 06 июня 2019

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

Я синхронизировал свой runnable с объектом, совместно используемым несколькими потоками, и явно синхронизировал метод, который я использую внутри, но результат программы всегда 3000.

Я попытался заблокировать класс Counter, но это ничего не изменит. Может ли кто-нибудь объяснить мне, почему ни одно из моих действий не работает в этом конкретном примере?

    public static void zad3() {
        var counter = new Counter();

        var toRun = new Runnable() {
            @Override
            public void run() {
                synchronized (counter) {
                    for (var i = 0; i < 1000; i++) {
                        counter.add(1);
                    }
                }
            }
        };

        var t1 = new Thread(toRun);
        var t2 = new Thread(toRun);
        var t3 = new Thread(toRun);

        t1.start();
        t2.start();
        t3.start();

        try {
            t1.join();
            t2.join();
            t3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("counter = " + counter.getCount());
   }
public class Counter {
    protected long count_ = 0;

    public synchronized void add(long value) {
        count_ += value;
    }

    public long getCount() {
        return count_;
    }
}

редактирование: Как и предполагалось, проблема заключалась в том, что каждый поток постоянно выполнялся 1000 раз. Мое решение:

        var toRun = new Runnable() {
            @Override
            public void run() {
                synchronized (counter) {
                    for (var i = counter.getCount(); i < 1000; i++) {
                        counter.add(1);
                    }
                }
            }
        };

Ответы [ 2 ]

2 голосов
/ 06 июня 2019

Итак, вы синхронизировали полный цикл for с переменной counter, что означает, что каждый поток будет запускать блок один раз.3 X 1000 = 3000

этот блок будет выполняться один раз для потока

 for (var i = 0; i < 1000; i++) {
                        counter.add(1);
 }

ОБНОВЛЕНИЕ: судя по вашим комментариям, вы хотите, чтобы прерывание на примере 1000 кода могло быть:

 t1.start();
 t2.start();
 t3.start();

while(counter.getValue()<1000) {
    Thread.sleep(20)
}

Еще одно предложение:

public class Incremetor extends Runnable {
   Counter counter;

public Incremetor(Counter counter) {
    this.counter = counter;
}
public void run() {
   counter.increment();
}

}

ExecutorService executorService = Executors.newFixedThreadPool(8); // this mean 8 threads in total to do your runnables.
for (int i=0;i<1000;++i) {
     executorService.submit(new Incrementor(counter));        
}
1 голос
/ 07 июня 2019

Итак, проблема в том, что вы позволяете каждому потоку делать 1000 приращений, поэтому вам нужно что-то вроде этого:

while (counter.getCount() < 1000) {
     counter.add(1);
}

Решение, которое вы предоставили, может дать вам правильный результат, но вы на самом деле увеличиваете счетчик только с 1 потока. Когда вы создаете синхронизированный блок с synchronized(object) { }, все потоки будут пытаться получить блокировку для этого блока, но только один будет. В вашем решении это означает, что первый поток, который получает блокировку, будет делать все 1000 приращений. Когда поток снимает блокировку и позволяет другим получить ее, работа уже сделана. Поэтому решение, которое фактически распределяет приращения между тремя потоками, не должно синхронизировать весь цикл for.

Если вы запустите предложенный мной цикл while, вы приблизитесь к 1000, но на самом деле оно может быть больше 1000. Не забудьте запустить вашу программу 10 раз или настроить тестовую функцию, которая запускает ее 100 раз. и сообщает обратно. Проблема в том, что с момента считывания counter.getCount() значение, возможно, уже изменилось другим потоком. Чтобы надежно всегда получить 1000, вы можете обеспечить исключительные права как на чтение, так и на запись на счетчик:

while (true) {
    synchronized (counter) {
        if (counter.getCount() < 1000) {
            counter.add(1);
        } else {
            break;
        }
    }
}

Обратите внимание, что приращение на одну переменную, подобное этой, медленное . Вы делаете только 1000, но попробуйте с миллиардом. Фактически, 3-поточная версия занимает (на моем ПК) 1m17s, тогда как простой последовательный цикл занимает ~ 1,2 секунды. Вы можете решить эту проблему, разделив рабочую нагрузку между потоками и позволив им работать на локальном счетчике с исключительными правами, а затем, наконец, добавить результаты.

...