Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Java: Общие вопросы > Количество повторений потоков


Автор: Pawl 19.2.2013, 09: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++, но как его переписать нужным образом, не знаю. Был бы благодарен, если просветите.
Спасибо!

Автор: jk1 19.2.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());
    }
}

Автор: Pawl 19.2.2013, 17:15
Цитата(jk1 @  19.2.2013,  16:26 Найти цитируемый пост)
В качестве решения предлагаю выкинуть CheesyCounter и воспользоваться стандартным CAS'ом:

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

Автор: jk1 19.2.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++;
    }
}

Автор: Pawl 19.2.2013, 19:13
Цитата(jk1 @  19.2.2013,  17:43 Найти цитируемый пост)
Чтобы получился пример можно исправить и исходный вариант:
 

Действительно, про синхронизацию в run я  как-то не подумал!

Автор: Viroman 27.2.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++;
     }
    }

Автор: jk1 27.2.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());
    }
}

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)