Количество повторений потоков

 
0
 
Java
ava
Pawl | 19.02.2013, 10:46
Доброго времени суток.
Создал такое приложеньице

public class CheesyCounter {
    private volatile int value;

    public int getValue() {
        return value;
    }

    public synchronized void increment() {
        value++;
    }
}

public class RunnerInc implements Runnable {
    private CheesyCounter counter;

    public RunnerInc(CheesyCounter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        int count = 0;
        while (counter.getValue() < 1500) {
            count++;
            counter.increment();
        }
        System.out.println(count);
    }
}

public class Main {
    public static void main(String ...args) {
        CheesyCounter counter = new CheesyCounter();
        RunnerInc run = new RunnerInc(counter);
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }
}

где цикл в методе run класса RunnerInc повторяется, пока value в классе CheesyCounter не станет равно 1500. Т. е. count в run должен стать в итоге равным 1500. Так и получается, если в Main запустить один поток. Но если потоков несколько, вывод может быть, к примеру, таким:

1002
0
499

т. е. суммарный count всех потоков равен 1501. Я так понимаю, что дело тут в операторе count++, но как его переписать нужным образом, не знаю. Был бы благодарен, если просветите.
Спасибо!
Ответы (6)
ava
jk1 | 19.02.2013, 16:26 #
Проблема в том, что тут должна быть атомарной операция "проверить, что счетчик меньше и увеличить его".
Вы же сделали атомарной только операцию увеличения счетчика. В итоге имеем такую последовательность:


... value = 1499

1. Thread 1 проверяет, что 1499 < 1500
2. Thread 2 проверяет, что 1499 < 1500
3. Thread 1 входит в synchronized-блок, увеличивает value на единицу и выходит
3. Thread 2 входит в synchronized-блок, увеличивает value на единицу и выходит

итого value = 1501

В качестве решения предлагаю выкинуть CheesyCounter и воспользоваться стандартным CAS'ом:


public class Main {
    public static void main(String ...args) {
        AtomicInteger counter = new AtomicInteger();
        RunnerInc run = new RunnerInc(counter);
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }
}

class RunnerInc implements Runnable {
    private AtomicInteger counter;

    public RunnerInc(AtomicInteger counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        while (true) {
            int value = counter.get();
            if (value < 1500000){
                counter.compareAndSet(value, value + 1);
            } else {
                break;
            }
        }
        System.out.println(counter.get());
    }
}
ava
Pawl | 19.02.2013, 17:15 #
Цитата (jk1 @  19.2.2013,  16:26 findReferencedText)
В качестве решения предлагаю выкинуть CheesyCounter и воспользоваться стандартным CAS'ом:

Спасибо за стандартное решение, просто хотелось сделать простенькие примерчики работы потоков с общим классом.
ava
jk1 | 19.02.2013, 17:43 #
Цитата


хотелось сделать простенькие примерчики работы потоков с общим классом



Чтобы получился пример можно исправить и исходный вариант:


public class Main {
    public static void main(String... args) {
        CheesyCounter counter = new CheesyCounter();
        RunnerInc run = new RunnerInc(counter);
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();

    }
}

class RunnerInc implements Runnable {
    private final CheesyCounter counter;

    public RunnerInc(CheesyCounter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (counter) {
                if (counter.getValue() < 150000) {
                    counter.increment();
                } else {
                    break;
                }
            }
        }
        System.out.println(counter.getValue());
    }
}

class CheesyCounter {
    private volatile int value;

    public int getValue() {
        return value;
    }

    public synchronized void increment() {
        value++;
    }
}
ava
Pawl | 19.02.2013, 19:13 #
Цитата (jk1 @  19.2.2013,  17:43 findReferencedText)
Чтобы получился пример можно исправить и исходный вариант:
  

Действительно, про синхронизацию в run я  как-то не подумал!
ava
Viroman | 27.02.2013, 17:20 #
Тема уже неактуальна, но это решение

    @Override
    public void run() {
        while (true) {
            synchronized (counter) {
                if (counter.getValue() < 150000) {
                    counter.increment();
                } else {
                    break;
                }
            }
        }
        System.out.println(counter.getValue());
    }

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


    public int getValue() {
        return value;
    }
переделать в
    public synchronized int getValue() {
        return value;
    }


либо вовсе заблокировать и чтение и запись на одном атомарном доступе.


    public int getValue() {
        return value;
    }
    public synchronized void increment() {
        value++;
    }
переделать в

    public int getValue() {
      synchronized(this) {
        return value;
      }
    }
    public void increment() {
      synchronized(this) {
        value++;
     }
    }
ava
jk1 | 27.02.2013, 17:25 #
Цитата


Приведёт к тому, что лишь один поток будет накручивать счётчик, который первым зашел в синхронизированный блок. Это ли то, что хотелось достичь, вот в чём вопрос..



Viroman, а проверить?)

Модифицируем немного решение и видим ID разных потоков, которые накручивают счетчик:

public class Main {
    public static void main(String... args) {
        CheesyCounter counter = new CheesyCounter();
        RunnerInc run = new RunnerInc(counter);
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }
}
class RunnerInc implements Runnable {
    private final CheesyCounter counter;
    public RunnerInc(CheesyCounter counter) {
        this.counter = counter;
    }
    @Override
    public void run() {
        while (true) {
            synchronized (counter) {
                if (counter.getValue() < 150000) {
                    counter.increment();
                } else {
                    break;
                }
            }
        }
        System.out.println(counter.getValue());
    }
}
class CheesyCounter {
    private volatile int value;
    public int getValue() {
        return value;
    }
    public synchronized void increment() {
        value++;
        System.out.println(Thread.currentThread().getId());
    }
}
Зарегистрируйтесь или войдите, чтобы написать.
Фирма дня
Вы также можете добавить свою фирму в каталог IT-фирм, и публиковать статьи, новости, вакансии и другую информацию от имени фирмы.
Подробнее
Участники
ava  Pawl   Viroman ava  jk1
advanced
Отправить