Локальные переменные и потоки
Помогите, пожалуйста, разобраться, что происходит "под капотом" у следующего кода:
public void incrementSharedAtomic() throws InterruptedException {
AtomicInteger sharedAtomic = new AtomicInteger(0);
Thread thread1 = new Thread(() -> {
sharedAtomic.incrementAndGet();
});
Thread thread2 = new Thread(() -> {
sharedAtomic.incrementAndGet();
});
thread1.start();
thread2.start();
Thread.sleep(3000);
System.out.println("Thread: result " + sharedAtomic.get());
//output "Thread: result 2"
}
Я читала, что у каждого потока свои копии локальных переменных, а в методе у нас переменные локальные, поэтому у меня получаются следующие рассуждения:
- Каждый поток (
thread1
иthread2
) получает свою копиюsharedAtomic
. AtomicInteger
является ссылочным типом и хранится в куче, поэтому потоки получают не копию самого объекта, а копию его ссылки на объект в куче.- Обе копии ссылки ссылаются на один и тот же объект в куче, поэтому
incrementAndGet()
изменяет значение у объекта в куче
Подскажите, пожалуйста, правильны ли рассуждения?
Правильно ли я понимаю, что значение sharedAtomic
будет видно обоим потокам (как если бы это было поле класса или поле с класса с volatile
) и если выполнить по 10000 вызовов в каждом потоке, то итоговое значение будет 20000?
Ответы (1 шт):
Да, все правильно, но стоит упомянуть еще пару моментов. В принципе вы их и так рассмотрите при дальнейшем изучении многопоточности и как с ними бороться, можно не вчитываться особо, но на всякий случай пусть будут.
- Типы копируются, но так же стоит иметь ввиду, что Java накладывает ограничения. Изначальная переменная используемая в lambda должна быть неизменяемая в дальнейшем.
Изменение ссылки приведет к ошибке, IDE такие моменты подсвечивает.
public static void main(String[] args) {
Food food = new Food();
Thread thread = new Thread(() -> {
food.setType(1);
// Error: Variable used in lambda expression should be final or effectively final
// food = new Food();
});
// Error: Variable used in lambda expression should be final or effectively final
// food = new Food();
}
Но если исходную переменную нужно будет изменить вне lambda функции, можно скопировать ссылку в другую переменную и использовать эту новую переменную в потоках.
- Класс AtomicInteger обеспечивает внутри атомарность операции.
При его использовании вы получите спокойно 20000. Если использовать volatile или просто обычный объект как в примере выше, то при определенных стечениях обстоятельств вы получите неверные результаты.
Thread thread = new Thread(() -> {
food.setPrice(food.getPrice() + 20);
});
В примере выше, если вызывается много потоков, то один может считать цену (допустим 100) и не успеть её обновить. Второй поток вклинится в код и считает "старую цену" (опять 100). В итоге вы получите не 140, а 120 из-за этого. Поэтому с многопоточностью стоит быть внимательным.