Concurrency - Java developer uchun juda muhim mavzu. Chunki backend’da bir vaqtning o‘zida ko‘p request, background task, database call, HTTP call, queue consumer, scheduled job ishlaydi.
Quyidagi mavzular kiritilgan: Thread, Runnable, Callable, synchronized, volatile, Executors, Future, CompletableFuture, Locks, Atomic classes, ThreadLocal, deadlock, livelock, race condition.
1. Concurrency nima?
Concurrency - bir nechta ish bir vaqtning ichida "navbatma-navbat" yoki "parallelga o‘xshab" bajarilishi.
Masalan:
1-thread: user ma'lumotini DBdan oladi
2-thread: email yuboradi
3-thread: payment status tekshiradi
4-thread: log yozadiBu ishlar bitta dastur ichida birga boshqariladi.
2. Thread nima?
Thread - Java dasturidagi alohida bajarilish oqimi.
Oddiy dasturda bitta main thread bo‘ladi:
public class Main {
public static void main(String[] args) {
System.out.println("Main thread ishlayapti");
}
}Thread yaratish:
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("Yangi thread ishlayapti");
});
thread.start();
System.out.println("Main thread ishlayapti");
}
}Natija tartibi har doim bir xil bo‘lmasligi mumkin:
Main thread ishlayapti
Yangi thread ishlayaptiyoki:
Yangi thread ishlayapti
Main thread ishlayaptiChunki threadlarni JVM va OS scheduler boshqaradi.
3. start() va run() farqi
Bu juda muhim.
Noto‘g‘ri:
Thread thread = new Thread(() -> {
System.out.println("Thread ishlayapti");
});
thread.run();Bu yangi thread ochmaydi. Oddiy method chaqiriladi.
To‘g‘ri:
thread.start();start() yangi thread ochadi va ichida run() bajariladi.
4. Runnable
Runnable - natija qaytarmaydigan task.
Runnable task = () -> {
System.out.println("Task bajarildi");
};
Thread thread = new Thread(task);
thread.start();Runnable ichida return yo‘q.
5. Callable
Callable - natija qaytaradigan task.
Callable<Integer> task = () -> {
return 10 + 20;
};Lekin Callable to‘g‘ridan-to‘g‘ri Thread bilan ishlamaydi. Uni odatda ExecutorService bilan ishlatamiz.
6. ExecutorService
Har safar new Thread() qilish yomon amaliyot bo‘lishi mumkin. Chunki ko‘p thread ochilsa, server qiynaladi.
Shuning uchun thread pool ishlatiladi.
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(() -> {
System.out.println("Task bajarildi");
});
executor.shutdown();Bu yerda:
Executors.newFixedThreadPool(5)5 ta threadli pool yaratadi. 100 ta task kelsa ham, bir vaqtning o‘zida 5 tasi ishlaydi, qolganlari navbatda turadi.
7. Callable + Future
ExecutorService executor = Executors.newFixedThreadPool(2);
Callable<Integer> task = () -> {
Thread.sleep(1000);
return 100;
};
Future<Integer> future = executor.submit(task);
Integer result = future.get();
System.out.println(result);
executor.shutdown();future.get() natija tayyor bo‘lguncha kutadi.
Ya’ni bu joyda blocking bor.
8. Blocking va non-blocking tushunchasi
Blocking
Thread kutib turadi.
Integer result = future.get();Natija kelmaguncha keyingi qatorga o‘tmaydi.
Non-blockingga yaqin yondashuv
CompletableFuture bilan callback ishlatamiz:
CompletableFuture.supplyAsync(() -> {
return "User data";
}).thenAccept(data -> {
System.out.println(data);
});Bu usulda natija kelganda keyingi action bajariladi.
9. CompletableFuture
CompletableFuture - async tasklar bilan ishlash uchun kuchli API.
Oddiy misol
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
return "Hello";
});
future.thenAccept(result -> {
System.out.println(result);
});thenApply
Natijani o‘zgartiradi.
CompletableFuture.supplyAsync(() -> {
return "ali";
}).thenApply(name -> {
return name.toUpperCase();
}).thenAccept(System.out::println);Natija:
ALIthenCompose
Bir async ish tugagandan keyin boshqasini boshlash.
CompletableFuture<User> userFuture = getUserAsync(1L);
CompletableFuture<Order> orderFuture = userFuture.thenCompose(user -> {
return getLastOrderAsync(user.id());
});Bu zanjirli async operatsiyalar uchun ishlatiladi.
thenCombine
Ikki async ishni parallel bajarib, natijasini birlashtirish.
CompletableFuture<String> userFuture =
CompletableFuture.supplyAsync(() -> "Ali");
CompletableFuture<Integer> orderCountFuture =
CompletableFuture.supplyAsync(() -> 5);
CompletableFuture<String> result = userFuture.thenCombine(orderCountFuture,
(user, orderCount) -> user + " has " + orderCount + " orders");
System.out.println(result.join());Natija:
Ali has 5 orders10. Race Condition
Race condition - bir nechta thread bitta shared data’ni bir vaqtda o‘zgartirganda noto‘g‘ri natija chiqishi.
Misol:
public class Counter {
private int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}count++ oddiy ko‘rinsa ham, aslida 3 bosqich:
1. count qiymatini o‘qi
2. 1 qo‘sh
3. qayta yozIkki thread bir vaqtda ishlasa, natija yo‘qolishi mumkin.
11. synchronized
synchronized bitta vaqtda faqat bitta thread kirishini ta’minlaydi.
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}Endi increment() methodiga bir vaqtda faqat bitta thread kiradi.
synchronized block
Butun methodni emas, faqat kerakli joyni lock qilish mumkin.
public class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
}Bu ko‘proq nazorat beradi.
12. volatile
volatile threadlar orasida qiymatning ko‘rinishini kafolatlaydi.
public class Worker {
private volatile boolean running = true;
public void stop() {
running = false;
}
public void work() {
while (running) {
// ishlaydi
}
}
}Agar volatile bo‘lmasa, bitta thread running = false qilsa ham, boshqa thread eski qiymatni ko‘rib turishi mumkin.
Muhim: volatile atomic emas
Bu xavfli:
private volatile int count = 0;
public void increment() {
count++;
}volatile visibility beradi, lekin count++ ni atomic qilmaydi.
Bunday holatda AtomicInteger kerak.
13. AtomicInteger
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();To‘liq misol:
public class Counter {
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
}Bu synchronized ishlatmasdan thread-safe counter beradi.
14. Lock
synchronized o‘rniga Lock ishlatish mumkin.
public class Counter {
private int count = 0;
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}finally shart. Aks holda exception bo‘lsa, lock ochilmay qolishi mumkin.
ReentrantLock qachon kerak?
synchronized yetmagan joylarda:
tryLock()kerak bo‘lsatimeout bilan lock olish kerak bo‘lsa
fairness kerak bo‘lsa
lock/unlock ustidan ko‘proq nazorat kerak bo‘lsa
Misol:
if (lock.tryLock()) {
try {
// code
} finally {
lock.unlock();
}
} else {
System.out.println("Lock olinmadi");
}15. ReadWriteLock
Ko‘p o‘qish, kam yozish bo‘lsa foydali.
ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();
Lock writeLock = rwLock.writeLock();Idea:
Bir nechta thread bir vaqtda o‘qishi mumkin.
Lekin yozish vaqtida hamma kutadi.16. ThreadLocal
ThreadLocal - har bir thread uchun alohida qiymat saqlaydi.
private static final ThreadLocal<String> currentUser = new ThreadLocal<>();
public void setUser(String user) {
currentUser.set(user);
}
public String getUser() {
return currentUser.get();
}Real loyihada qayerda uchraydi?
Security context
Request context
Transaction context
MDC logging
Tenant context
Lekin ehtiyot bo‘lish kerak. Thread pool bilan ishlaganda ThreadLocal tozalanmasa memory leak yoki noto‘g‘ri data chiqishi mumkin.
try {
currentUser.set("Ali");
// logic
} finally {
currentUser.remove();
}17. Deadlock
Deadlock - ikki yoki undan ko‘p thread bir-birini kutib qolishi.
Object lockA = new Object();
Object lockB = new Object();
Thread t1 = new Thread(() -> {
synchronized (lockA) {
synchronized (lockB) {
System.out.println("t1 done");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lockB) {
synchronized (lockA) {
System.out.println("t2 done");
}
}
});t1 lockA ni oladi, lockB ni kutadi.t2 lockB ni oladi, lockA ni kutadi.
Natija: ikkalasi ham qotadi.
Deadlockdan saqlanish
Locklarni har doim bir xil tartibda olish kerak.
Yaxshi:
synchronized (lockA) {
synchronized (lockB) {
// code
}
}Boshqa joyda ham shu tartib:
synchronized (lockA) {
synchronized (lockB) {
// code
}
}18. Livelock
Livelock - threadlar qotmaydi, lekin foydali ish ham qilmaydi.
Oddiy misol:
Ikki odam yo‘lakda to‘qnashdi.
Birinchisi chapga o‘tdi.
Ikkinchisi ham chapga o‘tdi.
Keyin ikkalasi o‘ngga o‘tdi.
Yana to‘qnashdi.
Harakat bor, lekin natija yo‘q.Concurrency’da ham shunaqa bo‘ladi: threadlar bir-biriga "yo‘l beraveradi", lekin ish tugamaydi.
19. Starvation
Starvation - bitta thread resursga hech qachon yetisha olmasligi.
Masalan, yuqori priority threadlar doim ishlaydi, past priority thread esa kutaveradi.
20. Thread Pool tanlash
FixedThreadPool
Executors.newFixedThreadPool(10);Doimiy thread soni.
CPU-heavy tasklar uchun:
thread count ≈ CPU core soniCachedThreadPool
Executors.newCachedThreadPool();Kerak bo‘lsa yangi thread yaratadi. Ehtiyot bo‘lish kerak, ko‘p task kelsa juda ko‘p thread ochishi mumkin.
SingleThreadExecutor
Executors.newSingleThreadExecutor();Bitta thread bilan ishlaydi. Tasklar ketma-ket bajariladi.
ScheduledExecutorService
Vaqti-vaqti bilan task bajarish uchun.
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
System.out.println("Har 5 sekundda ishlaydi");
}, 0, 5, TimeUnit.SECONDS);21. Virtual Threads
Java 21'da virtual threads production-ready bo‘ldi.
Oddiy platform thread OS threadga bog‘langan. Virtual thread esa juda yengil.
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> {
System.out.println("Virtual thread ishladi");
});
}Qachon foydali?
Virtual thread ayniqsa blocking I/O uchun foydali:
DB call
HTTP call
file read/write
external API call
ko‘p requestli backend
Qachon foydasizroq?
CPU-heavy tasklarda:
video processing
katta matematik hisoblash
encryption
image processing
Bunday holatda CPU core baribir limit bo‘ladi.
22. Spring Boot’da concurrency qayerda uchraydi?
Web requestlarda
Har bir HTTP request alohida thread’da ishlaydi.
@GetMapping("/users/{id}")
public UserDto getUser(@PathVariable Long id) {
return userService.getUser(id);
}Ko‘p request kelsa, ko‘p thread ishlaydi.
@Async
Spring’da methodni background thread’da ishlatish.
@Async
public void sendEmail(String email) {
// email yuborish
}Enable qilish kerak:
@EnableAsync
@Configuration
public class AsyncConfig {
}Lekin production’da default executor bilan qoldirish yaxshi emas. O‘zing thread pool sozlagan yaxshi.
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
executor.initialize();
return executor;
}23. Real hayotiy misol
Tasavvur qil: order yaratildi.
Bajariladigan ishlar:
Order DBga yoziladi
Payment service chaqiriladi
SMS yuboriladi
Email yuboriladi
Telegram notification yuboriladi
Log yoziladi
Hammasini bitta thread’da ketma-ket qilsak, request sekinlashadi.
Yaxshiroq yondashuv:
@Transactional
public OrderDto createOrder(CreateOrderRequest request) {
Order order = orderRepository.save(mapToOrder(request));
notificationService.sendOrderCreatedAsync(order.getId());
return mapToDto(order);
}Email, SMS, Telegram background’da ketadi. Lekin muhim joy: transaction tugamasdan turib async task DBdan orderni topolmasligi mumkin. Shuning uchun event yoki after commit mexanizmini ishlatish yaxshiroq.
24. Eng ko‘p xatolar
1. Har task uchun new Thread() qilish
Yomon:
new Thread(() -> sendEmail()).start();Yaxshi:
executorService.submit(() -> sendEmail());2. ExecutorService’ni yopmaslik
Yomon:
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(task);Yaxshi:
executor.shutdown();3. Shared mutable state
Yomon:
List<String> list = new ArrayList<>();
executor.submit(() -> list.add("A"));
executor.submit(() -> list.add("B"));ArrayList thread-safe emas.
Yaxshiroq:
List<String> list = Collections.synchronizedList(new ArrayList<>());yoki:
List<String> list = new CopyOnWriteArrayList<>();4. volatile bilan counter qilish
Yomon:
volatile int count;
count++;Yaxshi:
AtomicInteger count = new AtomicInteger();
count.incrementAndGet();5. Lockni ochmaslik
Yomon:
lock.lock();
count++;
lock.unlock();Agar count++ joyida exception bo‘lsa, lock ochilmaydi.
Yaxshi:
lock.lock();
try {
count++;
} finally {
lock.unlock();
}25. Interview savollari
Savol: Runnable va Callable farqi?
Runnable natija qaytarmaydi va checked exception tashlay olmaydi.Callable natija qaytaradi va exception tashlashi mumkin.
Savol: synchronized va volatile farqi?
synchronized mutual exclusion beradi: bir vaqtda bitta thread ishlaydi.volatile visibility beradi: bitta thread yozgan qiymatni boshqasi ko‘radi.
Savol: AtomicInteger qanday ishlaydi?
U lock ishlatmasdan CAS orqali atomic update qiladi.
Savol: Deadlock nima?
Threadlar bir-birida turgan lockni kutib qolib, hech biri davom eta olmasa deadlock bo‘ladi.
Savol: Future va CompletableFuture farqi?
Future oddiy natija kutish uchun. get() blocking qiladi.CompletableFuture async chain, callback, combine, compose kabi imkoniyatlar beradi.
Savol: Virtual thread nimaga kerak?
Ko‘p blocking I/O tasklarni kamroq resurs bilan bajarish uchun. Ayniqsa HTTP server, DB call, external API calllarda foydali.
26. Qisqa xulosa
Java developer concurrency bo‘yicha shularni tushunishi kerak:
Mavzu | Nima uchun kerak |
|---|---|
Thread | Parallel ish oqimini tushunish |
Runnable | Natijasiz task |
Callable | Natijali task |
ExecutorService | Thread pool boshqarish |
Future | Kelajakdagi natija |
CompletableFuture | Async pipeline |
synchronized | Critical section himoyasi |
volatile | Visibility |
Atomic classes | Lock-free thread-safe update |
Lock | Kengroq lock nazorati |
ThreadLocal | Threadga xos context |
Deadlock | Qotib qolish muammosi |
Race condition | Shared data buzilishi |
Virtual threads | Yengil concurrency modeli |