Java asoslari 11-qism Concurrency & Threading

30.06.2026 | Muallif: Jaxongir a.k.a | Kategoriya: Java tili | 17 daqiqa o'qish

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 yozadi

Bu 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 ishlayapti

yoki:

Yangi thread ishlayapti
Main thread ishlayapti

Chunki 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:

ALI

thenCompose

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 orders

10. 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 yoz

Ikki 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‘lsa

  • timeout 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 soni

CachedThreadPool

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:

  1. Order DBga yoziladi

  2. Payment service chaqiriladi

  3. SMS yuboriladi

  4. Email yuboriladi

  5. Telegram notification yuboriladi

  6. 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