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

A создал этот тестовый пример, который воссоздает то, что я хочу сделать.

public class Test {

    public static void main(String args[]) {
        AtomicInteger total = new AtomicInteger(0);
        for (int i = 0; i < 10; i++) {

            DoThing doThing = new DoThing();

            Thread thread = new Thread(doThing);
            thread.start();
            total.addAndGet(doThing.getTally());
        }

        System.out.println(total.get());
    }
}

class DoThing implements Runnable {

    int tally = 0;
    @Override
    public void run() {

        for(int i = 0; i< 100; i++) {
            tally++;
        }

        System.out.println("Tally is " + tally);

    }

    public int getTally() {
        return tally;
    }
}

Тем не менее, это выводит:

Tally is 100
Tally is 100
Tally is 100
Tally is 100
Tally is 100
Tally is 100
Tally is 100
Tally is 100
0
Tally is 100
Tally is 100

Когда я хотел бы, чтобы конечный результат был 1000. Как я могу увеличивать потоки?

Заранее спасибо.

0
LivingRobot 22 Фев 2018 в 22:58

4 ответа

Лучший ответ

Попробуйте с этим:

public static void main(String args[]) {
    AtomicInteger tally = new AtomicInteger(0);
    List<Thread> threadList = new ArrayList<Thread>();
    for (int i = 0; i < 10; i++) {
        Thread t = new Thread(new DoThing(tally));
        t.start();
        threadList.add(t);
    }
    for (Thread t : threadList) {
        try { t.join(); } catch (Exception e){}
    }
    System.out.println("Total tally: " + tally.get());
}

public static class DoThing implements Runnable {
    private static final Random rand = new Random();
    private final AtomicInteger tally;

    public DoThing(AtomicInteger tally) {
        this.tally = tally;
    }

    @Override public void run() {
        for (int i = 0; i < 100; i++) {
            int currTally  = tally.incrementAndGet();
            System.out.println("Thread " + Thread.currentThread().getName() + ": " + currTally);
            // Random sleep to show that your threads are properly concurrently incrementing
            try { Thread.sleep(rand.nextInt(10)); } catch (Exception e) {}
        }
    }
}

Корень вашей проблемы в том, что вы неправильно понимаете, как использовать AtomicInteger, вы относитесь к нему как к обычному int и к нему не обращаются одновременно.

Также getTally() является условием гонки, пока вы не убедитесь, что поток завершен, вызвав Thread.join().

Таким образом, вы можете следить за обновлениями, обновляя один и тот же экземпляр AtomicInteger всеми Runnable в потоках, и вы можете убедиться, что у вас есть правильная сумма, ожидая, пока все потоки закончите их подсчет, join() перед тем, как получить счет.

2
xtratic 22 Фев 2018 в 20:45

В вашем примере кода, total доступен только из основного потока. Создание Atomic не повлияет на результат. Вы должны передать атомарное значение вашим потокам и увеличить значение там. Или используйте LongAdder (метод приращения).
И вы должны дождаться завершения всех потоков, прежде чем печатать атомарное значение в основном потоке.
Если вы хотите использовать низкоуровневую блокировку, вы можете использовать CyclicBarrier, чтобы основной поток ожидал все потоки.

2
miskender 22 Фев 2018 в 20:06
CountDownLatch latch = new CountDownLatch(10);
List<DoThing> things = new ArrayList();
AtomicInteger total = new AtomicInteger(0);
    for (int i = 0; i < 10; i++) {

        DoThing doThing = new DoThing(latch);
        things.add(doThing);
        Thread thread = new Thread(doThing);
        thread.start();
        total.addAndGet(doThing.getTally());
    }
// Wait till all the threads are done.
// Each thread counts the latch down
latch.await()
int total = 0;

// Calculate sum after all the threads are done.
for (DoThing thing: things) {
    total += thing.getTally();
}
System.out.println(total);



class DoThing implements Runnable {

    private CountDownLatch latch;

    public DoThing(CountDownLatch latch) {
        this.latch = latch;
    }

    int tally = 0;
    @Override
    public void run() {

        for(int i = 0; i< 100; i++) {
            tally++;
        }
        latch.countDown();
        System.out.println("Tally is " + tally);

    }

    public int getTally() {
        return tally;
    }
}
1
ekaerovets 22 Фев 2018 в 20:09

Да, есть гонка данных. Гонка между основным потоком, вызывающим doThing.getTally(), и рабочим потоком, запускающимся. Похоже, что каждый раз, когда ваш основной поток может надежно получить «счет» от каждого работника, прежде чем работник даже получит шанс войти в свой цикл for. Это может произойти еще до того, как работник вызовет свой метод run().

Ваш главный поток должен join рабочих:

  • Создайте и запустите десять рабочих потоков и добавьте каждый из них в List<Thread>.
  • Затем в отдельном цикле вызовите t.join() для каждого потока t в списке. Функция t.join() ожидает, пока поток t завершит свою работу.
  • Наконец, получите подсчеты и сложите их.
2
Solomon Slow 22 Фев 2018 в 20:14