LINUX.ORG.RU

Написал простейший код на java, многопоточный и сразу фейл

 


0

2

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

class Watcher implements Runnable {
 public static int i=0;
public void run() {
final int NUM_ITER = 10000;
for (int z = 0; z < NUM_ITER; z++) {
    i++;
    }

  }
}

public class Main {

    public static void main(String[] args) {
        Watcher watcher = new Watcher();
        try {
            Thread thread1 = new Thread(watcher, "thread1");
            Thread thread2 = new Thread(watcher, "thread2");

            thread1.start();
            thread2.start();

            thread2.join();
            thread1.join();

            System.out.println("Final result: " + Integer.toString(watcher.i));
        }
        catch (Exception e) {
            System.out.println("Exception");
        }
    }
}

В общем сорян, что-то разметка разъезажется и так и так.


переменная типа int

s/int/AtomicInteger

гарантируется атомарное чтение/запись

Вы либо крестик снимите (добавьте volatile), либо трусы наденьте (используйте AtomicInteger).

cocucka ★★★★☆
()

Жаль тебя.
Тебе только предстоит освоить атомарные типы или воткнуть synchronized там, где ты дергаешь один общий инт из разных потоков

rukez ★★★★
()
Ответ на: комментарий от rukez

Ну я только начал осваивать это, Java то вроде современный язык, а надо как в дедовском ++ что-то охранять от совместных модификаций, могли бы уж все автоматом сделать. Специально написал, что бы проверить будут ли ошибки или нет.

da17
() автор топика
Последнее исправление: da17 (всего исправлений: 1)

гарантируется атомарное чтение/запись

Только i++ это чтение плюс запись, т.е. две операции, т.е. уже не атомарно.

chkalov
()

День добрый.

У вас разметка кода немного поехала, вы BBCode-тег [code] вставили не туда, куда нужно:

class Watcher implements Runnable { public static int i=0; public void run() { final int NUM_ITER = 10000; for (int z = 0; z < NUM_ITER; z++) { i++; }

} } [code] public class Main {

что если переменная типа int то гарантируется атомарное чтение/запись и никакими мьютексами ее защищать не надо.

Операция i++, кстати, не является атомарной, это составная операция.

Ну я только начал осваивать это, Java то вроде современный язык, а надо как в дедовском ++ что-то охранять от совместных модификаций, могли бы уж все автоматом сделать.

Современный язык с кучей синтаксического сахара – это Kotlin, рекомендую попробовать его.

EXL ★★★★★
()

потому что инккеремент/декремент на атомарны. не умеет процессор инкрементировать в памяти одной командой. если б умел - был бы атомарен.

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)

Так работает:

import java.util.concurrent.atomic.AtomicInteger;

class Watcher implements Runnable {
    public static final AtomicInteger num = new AtomicInteger();

    public void run() {
        final int NUM_ITER = 10000;
        for (int z = 0; z < NUM_ITER; z++) {
            num.incrementAndGet();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Watcher watcher = new Watcher();
        try {
            Thread thread1 = new Thread(watcher, "thread1");
            Thread thread2 = new Thread(watcher, "thread2");

            thread1.start();
            thread2.start();

            thread2.join();
            thread1.join();

            System.out.println("Final result: " + Watcher.num);
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }
}

bbk123 ★★★★★
()

если переменная типа int то гарантируется атомарное чтение/запись

Не гарантируется. Как минимум тебе там надо вставить volatile. Но тебе и это не поможет, т.к. ты не просто читаешь/записываешь, а производишь составную операцию: сначала читаешь, потом выполняешь другие команды, потом записываешь. И ты ожидаешь, что эта составная операция будет почему-то атомарной. Не будет.

никакими мьютексами ее защищать не надо.

Любую разделяемую память нужно защищать любо критическими секциями, либо использовать атомарные операции (например класс AtomicInteger). Очень редко можно обойтись volatile.

могли бы уж все автоматом сделать

Это привело бы к неописуемым тормозам. Если вообще возможно. К счастью не сделали.

Я тебе советую прочитать книгу Java Concurrency in Practice. Многопоточность это сложно и ты сам не разберёшься.

Legioner ★★★★★
()
Последнее исправление: Legioner (всего исправлений: 1)

читал, что если переменная типа int то гарантируется атомарное чтение/запись

Гарантируется, но разве там написано что гарантируется атомарное чтение+запись?

ya-betmen ★★★★★
()

если хочешь действительно разобраться, читай книгу Java Concurrency in Practice

stevejobs ★★★★☆
()
Ответ на: комментарий от da17

могли бы уж все автоматом сделать.

Не могли. Ты просто не достаточно разбираешься в вопросе, потому не знаешь почему.

P.S. Чтобы тебе в следующий раз на форумах не хамили, как это делаю я, избегай советовать как что-то нужно сделать если не разбираешься в вопросе. Просто спрашивай прямо «почему вот так сделали?»

vertexua ★★★★★
()
Последнее исправление: vertexua (всего исправлений: 1)
Ответ на: комментарий от vertexua

Да это не имеет никакого значения, как вопрос не задай все время кто-нибудь нахамит. Ну и вопросы задают что бы из состояния «недостаточно разбираешь» перейти в состояние «чуть-чуть разбираешься».

da17
() автор топика
Последнее исправление: da17 (всего исправлений: 1)
Ответ на: комментарий от spock74

Над значением в регистре, туда значение нужно записать, а потом считать.

anonymous
()

Сколько много слов в вашей жабе, выглядет вырвиглазно, начинает немного тошнить )

pavlick ★★
()
Ответ на: комментарий от spock74

а как же команда inc в x86?

она не атомарная и инкремент только на 1 . на нижнем уровне все равно берется в регистр, там увеличивается, а потом пишется обратно. она атомарна с префиксом lock, если адресное выражение для приемника представимо в виде ее операнда.

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)

ребят, о чём вы вообще говорите, какие команды процессора? код на джаве выполняется в виртуальной машине и в ней всё можно сделать атомарно.

anonymous
()
Ответ на: комментарий от anonymous

ребят, о чём вы вообще говорите, какие команды процессора? код на джаве выполняется в виртуальной машине и в ней всё можно сделать атомарно.

а jit-компилятора там уже нет? надо смотреть набор команд жава машины, и есть ли декларация атомарности инкремента, если он там вообще есть. естесно это атомарность на уровне потока исполнения жавамашины, а не реального проца.

но если например язык си или плюсы, то инкремент на 1 можно сделать атомарным на интеле, сгенерив команду lock inc mem. если адрсное выражение влезет в mem. и если разрабы кодогенератора этим озаботятся. например зная что переменная определена как volatile. что как бы указывает, что ее меняют из разных потоков.

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

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)

Вот почему начинающие кодеры на жабке считают, что многопоток - это «вау круто» и потом бегут всей толпой отстреливать себе ноги и зажимать яйца в дверных проёмах?

crutch_master ★★★★★
()

если переменная типа int то гарантируется атомарное чтение/запись

Для архитектуры x86_64 и если этот int выровнен по границе в 32 бита в памяти. В общем случаи атомарным он быть не обязан, используй специализированный тип.

dvetutnev
()
Ответ на: комментарий от crutch_master

и зажимать яйца в дверных проёмах?

Зато можно прищемить два яйца сразу двумя дверьми, разве не круто?

Nervous ★★★★★
()
Ответ на: комментарий от da17

Ну я только начал осваивать это, Java то вроде современный язык, а надо как в дедовском ++ что-то охранять от совместных модификаций, могли бы уж все автоматом сделать. Специально написал, что бы проверить будут ли ошибки или нет.

Автоматом без проблем но с потерей производительности - просто засинхронизируй все внутри run по i (придётся ее Integer’ом сделать) - будет однопоток по сути :-)

Поясню - «автоматом» в синхронизацию не получится нигде - либо, по итогам, все сведётся к copy-on-write либо к однопотоку. По хорошему, в любом языке ты должен сам определять какие части кода синхронизировать

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

rukez ★★★★
()
Ответ на: комментарий от da17

Java то вроде современный язык, а надо как в дедовском ++ что-то охранять от совместных модификаций

(def a (atom 0)) и потом спокойно (swap! a inc) хоть в хренадцать потоков. Ничего вручную блокировать не надо.

Nervous ★★★★★
()
Последнее исправление: Nervous (всего исправлений: 1)
Ответ на: комментарий от Legioner

Java Concurrency in Practice - этого хватит что бы разобраться? Что еще можно почитать по этой теме (кроме Таненбаума)

da17
() автор топика
Ответ на: комментарий от da17

Думаю, для подавляющего большинства практических целей больше ничего не нужно.

Legioner ★★★★★
()
Ответ на: комментарий от crutch_master

Правильно думают. В современном мире уже в любом тостере 32 потока. Многопоточный код это единственный разумный способ выжимать нужную производительность.

Legioner ★★★★★
()
Ответ на: комментарий от da17

Java то вроде современный язык, а надо как в дедовском ++ что-то охранять от совместных модификаций, могли бы уж все автоматом сделать.

Пиши на Scala, используя аппликативное программирование.

LongLiveUbuntu ★★★★★
()
Ответ на: комментарий от LongLiveUbuntu

У меня итоговая цель, сделать надежно приложение под андроид. Так что мне нужна исключительно джава.

da17
() автор топика
Ответ на: комментарий от Legioner

Правильно думают. В современном мире уже в любом тостере 32 потока. Многопоточный код это единственный разумный способ выжимать нужную производительность.

Получая в процессе выжимания рандомные дедлоки, утечки и, в лучшем случае, тормоза с недоумением, «а чего это оно? тут же 32 потока, значит работать должно в 32 раза быстрее».

crutch_master ★★★★★
()

Зачем вообще в 2021 менять значение переменной в разных тредах? Для этого нет применения.

system-root ★★★★★
()
Ответ на: комментарий от system-root

Что бы увидеть, что все плохо и как следует запомнить, что так делать нельзя. Обучение через эксперимент.

da17
() автор топика
Ответ на: комментарий от anonymous

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

LongLiveUbuntu ★★★★★
()
Ответ на: комментарий от seiken

Где об этом в посте?

Давай пойдём от обратного. ТС сделал херню с многопоточкой. Зачем? Если бы он считал, что это конченое поле костылей, стал бы он это пробовать?

crutch_master ★★★★★
()
Ответ на: комментарий от crutch_master

Да я просто сделал, посмотреть, действительно ли в условиях конкуренции за ресуср без всяких защит будут неверные результаты получаться и кстати нет, примерно в 2/3 случаев ответ получается правильный. В остальных случаях значение получается меньше. Так что вот допустим если я сажаю космический корабль на планету и мне нужно рассчитать траекторию, то у меня допустим есть три варианта: ничего не считать и разбиться, рассчитать корректные значения с вероятностью 60%. Ситуация и так плохая, надо выбрать наименьшее зло, если например по времени никак не укладываюсь.

da17
() автор топика
Последнее исправление: da17 (всего исправлений: 3)
Ответ на: комментарий от seiken

Это вообще не то, за что стоит в первую очередь браться.

crutch_master ★★★★★
()
Ответ на: комментарий от da17

Так что вот допустим если я сажаю космический корабль на планету

Тебя никто до написания такого софта всё равно никто не допустит с такими-то результатами. Вероятность того, что тебе надо резко сваять многопоток и это - вопрос жизни и смерти, тождественна нулю, а при разработке какой-то ИС - это свинья, которая насрёт тебе столько дерьма, что не будешь успевать его разгребать.

если например по времени никак не укладываюсь.

Херовая многопоточка может работать в разы медленнее самого тупого однопоточного кода. Тем более, если у тебя там постоянно всё шарится и пытается синхронизироваться между потоками с выдачей рандомных результатов. Только лишь переключение контекста потока не такая уж и дешевая операция, как ты думаешь и твой i++ два потока по очереди будут считать медленнее, чем в один.

примерно в 2/3 случаев ответ получается правильный

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

crutch_master ★★★★★
()
Последнее исправление: crutch_master (всего исправлений: 5)

прочитай Java Concurrency in Practice

anonymous
()

Создавай массив и пиши в первом потоке результат в первый(нулевой) элемент массива, во втором потоке пиши в следующий элемент этого же массива. Не тупи 😀

menangen ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.