Поток небезопасного уменьшения / увеличения - почему в основном положительный?
Я задаюсь вопросом о результате небезопасного уменьшения / увеличения в потоках Java, поэтому есть моя программа:
Основной класс:
public class Start {
public static void main(String[] args) {
int count = 10000000, pos = 0, neg = 0, zero = 0;
for (int x=0; x<10000; x++) {
Magic.counter = 0;
Thread dec = new Thread(new Magic(false, count));
Thread inc = new Thread(new Magic(true, count));
dec.start();
inc.start();
try {
inc.join();
dec.join();
} catch (InterruptedException e) {
System.out.println("Error");
}
if (Magic.counter == 0)
zero++;
else if (Magic.counter > 0)
pos++;
else
neg++;
}
System.out.println(Integer.toString(neg) + "\t\t\t" + Integer.toString(pos) + "\t\t\t" + Integer.toString(zero));
}
}
Класс темы:
public class Magic implements Runnable {
public static int counter = 0;
private boolean inc;
private int countTo;
public Magic(boolean inc, int countTo) {
this.inc = inc;
this.countTo = countTo;
}
@Override
public void run() {
for (int i=0;i<this.countTo;i++) {
if (this.inc)
Magic.counter++;
else
Magic.counter--;
}
}
}
Я запускал программу несколько раз, и всегда получал гораздо более положительный результат, чем отрицательный. Я также пытался изменить порядок запуска потоков, но это ничего не изменило. Некоторые результаты:
Number of results < 0 | Number of results > 0 | Number of results = 0
1103 8893 4
3159 6838 3
2639 7359 2
3240 6755 5
3264 6728 8
2883 7112 5
2973 7021 6
3123 6873 4
2882 7113 5
3098 6896 6
2 ответа
Бьюсь об заклад, вы увидите совершенно противоположное поведение со следующим изменением (то есть, переверните ветви, не меняя ничего другого):
if (this.inc)
Magic.counter--; // note change, and lie about `this.inc`
else
Magic.counter++;
Если это правда, что это может указывать на это, указывает на взаимодействие потоков?
Теперь, для удовольствия, сделать Magic.counter
volatile - [как] меняются результаты?
Как насчет удаления volatile
и окружающие if/else
с lock
? (A lock
обеспечивает полный забор памяти и устанавливает критическую область. Это всегда должно давать идеальные результаты.)
Удачного кодирования.
Что нужно учитывать:
- Код только смотрит на значение меньше или больше нуля, а не на общее отклонение / отклонение: все, что нужно, чтобы склонить чашу весов, - это +1 или -1. (Это может быть более полезным для расширения собранных данных.)
- Требуется немного больше времени для выполнения ветви "else", поскольку требуется переход; обычно это не проблема, но более 10 миллионов циклов... один или два не так много.
- Отсутствие летучих заборов / ограждений оставляет значительную слабость в видимости
Magic.counter
переменная. (Я считаю, что соответствующая JVM может дать гораздо худшие результаты...) ++
а также--
операторы по своей природе не атомарны.- Чередование потоков обычно "недетерминировано"; меньше, если выполняется на нескольких ядрах.
Вообще говоря, это связано с тем, как работает модель памяти Java. Вы получаете доступ к общим переменным в двух разных потоках без синхронизации. Ни переменная не объявлена как volatile, ни вы не выполняете операции Atomar. Отсутствие координационных и атомарных или изменчивых переменных приведет к внутренней оптимизации многопоточного кода, выполняемого JVM при выполнении. Кроме того, энергонезависимые переменные, которые не пересекли барьер памяти (т.е. synchronized
) приведет к кэшируемым значениям для каждого потока и, следовательно, к двум конфликтующим локальным копиям потоков внутри их кэшей.
Принимая во внимание отсутствие последовательной модели согласованности в Java, сложные оптимизации во время выполнения и особенности используемой JVM и базовой системы (одноядерная или многоядерная, гиперпоточность), невозможно предсказать результат детерминистически - только потому, что он нарушает несколько -предметные соглашения для модели языка Java. Выполнение точно такого же кода на той же машине может привести к аналогичным результатам, но из-за эффектов планирования потоков, использования ЦП другими процессами ОС и т. Д. Они вряд ли будут точно такими же.
Вот некоторые ресурсы о JMM: http://www.cs.umd.edu/~pugh/java/memoryModel/