Какие знаешь ключевые слова для многопоточности в Java?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Ключевые слова для многопоточности в Java
Многопоточность — это способность программы выполнять несколько задач одновременно. Java имеет встроенную поддержку многопоточности через несколько ключевых слов и механизмов синхронизации.
1. synchronized
Используется для синхронизации доступа к общим ресурсам. Только один поток может выполнять synchronized блок одновременно.
// Синхронизация метода (весь метод как critical section)
public class Counter {
private int count = 0;
public synchronized void increment() {
count++; // Только один поток за раз
}
public synchronized int getCount() {
return count;
}
}
// Синхронизация блока (более гибко)
public class BankAccount {
private double balance = 0;
private Object lock = new Object();
public void deposit(double amount) {
synchronized(lock) { // Блокирует только необходимую часть
balance += amount;
System.out.println("Deposited: " + amount);
}
}
public void withdraw(double amount) {
synchronized(lock) {
if (balance >= amount) {
balance -= amount;
System.out.println("Withdrew: " + amount);
}
}
}
}
// Синхронизация на объекте класса
public class SingletonWithSync {
private static SingletonWithSync instance;
public static SingletonWithSync getInstance() {
synchronized(SingletonWithSync.class) {
if (instance == null) {
instance = new SingletonWithSync();
}
}
return instance;
}
}
Проблемы: может привести к deadlock'ам, performance overhead, неявная блокировка.
2. volatile
Гарантирует, что значение переменной видно всем потокам, не кэшируется в CPU кэше.
public class VolatileExample {
// БЕЗ volatile: поток может кэшировать значение
private boolean shouldStop = false;
// С volatile: всегда читает актуальное значение
private volatile boolean running = true;
public void startWorker() {
new Thread(() -> {
while (running) { // Видит обновления из других потоков
doWork();
}
System.out.println("Worker stopped");
}).start();
}
public void stopWorker() {
running = false; // Все потоки сразу увидят true
}
}
// Volatile для флагов
public class Application {
private volatile boolean applicationStarted = false;
public void start() {
applicationStarted = true;
}
public boolean isRunning() {
return applicationStarted; // Всегда актуальное значение
}
}
Используй для: флаги, счетчики, простые значения. НЕ используй: замену synchronized, для сложной синхронизации.
3. wait() и notify()
Механизм внутри synchronized для коммуникации между потоками через monitor.
public class ProducerConsumer {
private Queue<Integer> buffer = new LinkedList<>();
private int maxSize = 10;
public synchronized void produce(int value) throws InterruptedException {
while (buffer.size() == maxSize) {
wait(); // Ждет, пока буфер освободится
}
buffer.add(value);
System.out.println("Produced: " + value);
notifyAll(); // Пробуждает все ждущие потоки
}
public synchronized int consume() throws InterruptedException {
while (buffer.isEmpty()) {
wait(); // Ждет, пока буфер заполнится
}
int value = buffer.poll();
System.out.println("Consumed: " + value);
notifyAll(); // Пробуждает производителей
return value;
}
}
// wait() vs notify()
wait(); // Отпускает монитор и ждет
notify(); // Пробуждает ОДИН ждущий поток (неопределенный)
notifyAll(); // Пробуждает ВСЕ ждущие потоки (безопаснее)
4. Ключевые слова в Thread классе
Thread.sleep(long millis) — паузирует текущий поток:
public class TimedTask {
public static void main(String[] args) throws InterruptedException {
System.out.println("Start");
Thread.sleep(2000); // Ждет 2 секунды
System.out.println("End"); // Выполнится через 2 сек
}
}
Thread.join() — ждет завершения другого потока:
Thread workerThread = new Thread(() -> {
System.out.println("Worker started");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Worker finished");
});
workerThread.start();
workerThread.join(); // Главный поток ждет завершения worker'а
System.out.println("Main continues");
// Output:
// Worker started
// (2 seconds pause)
// Worker finished
// Main continues
Thread.yield() — подсказывает scheduler'у дать возможность другим потокам:
for (int i = 0; i < 1000000; i++) {
doWork();
if (i % 1000 == 0) {
Thread.yield(); // Может позволить другим потокам выполниться
}
}
5. Java Concurrent API (modern approach)
Модерная альтернатива synchronized — более гибкая и мощная.
ReentrantLock — явная блокировка (лучше контроль):
import java.util.concurrent.locks.ReentrantLock;
public class ModernCounter {
private int count = 0;
private ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock(); // ВАЖНО: unlock в finally
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
// С try-with-resources (если используется StampedLock)
public class StampedLockExample {
private StampedLock lock = new StampedLock();
private int value = 0;
public void update(int newValue) {
long stamp = lock.writeLock(); // Захватить write lock
try {
value = newValue;
} finally {
lock.unlockWrite(stamp); // Освободить
}
}
public int read() {
long stamp = lock.readLock(); // Read lock (может быть несколько)
try {
return value;
} finally {
lock.unlockRead(stamp);
}
}
}
Condition — для координации (замена wait/notify):
public class BoundedBuffer<T> {
private Queue<T> buffer = new LinkedList<>();
private int maxSize;
private ReentrantLock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
public BoundedBuffer(int maxSize) {
this.maxSize = maxSize;
}
public void put(T item) throws InterruptedException {
lock.lock();
try {
while (buffer.size() == maxSize) {
notFull.await(); // Как wait()
}
buffer.add(item);
notEmpty.signalAll(); // Как notifyAll()
} finally {
lock.unlock();
}
}
public T get() throws InterruptedException {
lock.lock();
try {
while (buffer.isEmpty()) {
notEmpty.await();
}
T item = buffer.poll();
notFull.signalAll();
return item;
} finally {
lock.unlock();
}
}
}
AtomicInteger, AtomicReference — потокобезопасные переменные без блокировок (lock-free):
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class AtomicCounter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // Атомарная операция
}
public int getCount() {
return count.get();
}
}
public class AtomicLinkedList {
private AtomicReference<Node> head = new AtomicReference<>();
public void add(int value) {
Node newNode = new Node(value);
while (true) {
Node oldHead = head.get();
newNode.next = oldHead;
if (head.compareAndSet(oldHead, newNode)) {
return; // Успешно добавили
}
// Retry если другой поток изменил head
}
}
}
Сравнение подходов
| Подход | Плюсы | Минусы | Используй для |
|---|---|---|---|
| synchronized | Простой синтаксис | Deadlock риск, performance | Простая синхронизация |
| volatile | Легкий, без overhead | Только для простых случаев | Флаги, числовые значения |
| ReentrantLock | Контроль, tryLock() | Сложнее, нужен finally | Сложная синхронизация |
| Atomic* | Lock-free, быстро | Сложная логика | Счетчики, ссылки |
| concurrent collections | Готовые thread-safe | Не все случаи покрывают | ConcurrentHashMap, CopyOnWriteArrayList |
Best Practices для многопоточности
- Избегай synchronized если можешь:
// Плохо: synchronized везде
public synchronized void method1() {
// много кода
}
// Хорошо: только критичная секция
public void method1() {
// некритичный код
synchronized(lock) {
// только критичная часть
}
}
- Используй concurrent коллекции:
// Плохо
Map<String, Integer> map = new HashMap<>();
synchronized(map) { // Каждый раз блокировка
map.put("key", 1);
}
// Хорошо
Map<String, Integer> map = new ConcurrentHashMap<>(); // Уже потокобезопасна
map.put("key", 1);
- Используй ExecutorService для потоков:
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
final int taskId = i;
executor.submit(() -> doTask(taskId));
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
Многопоточность — сложная тема, требующая внимания к деталям. Ошибки в многопоточных системах очень трудно воспроизвести и отладить.