Bu rehber Java'da threading konusunu OS Thread'lerden başlayarak Virtual Thread'lere kadar kapsamlı bir şekilde ele alır.
- OS Thread Temelleri
- Java Platform Threads
- Thread Synchronization
- Executor Framework
- Concurrent Collections
- Modern Threading (Java 8+)
- Virtual Threads (Java 21+)
- Best Practices Özeti
- Her OS thread ~1-2MB stack memory kullanır
- Thread oluşturma maliyeti yüksektir (sistem call, memory allocation)
- Context switching CPU overhead'i oluşturur
- OS limitler nedeniyle binlerce thread'den fazla oluşturulamaz
Memory: ~1-2MB per thread
Creation: Expensive (system calls)
Context Switch: CPU overhead
Limit: ~Few thousands
Management: OS level
⚠️ Thread oluşturmayı minimize edin⚠️ Thread pool pattern'ini kullanın⚠️ Context switching maliyetini göz önünde bulundurun
// ✅ İYİ - Runnable interface
Thread thread = new Thread(() -> {
// task logic
});
// ❌ KÖTÜ - Thread extend etme
class MyThread extends Thread {
// Inheritance waste
}
// ✅ İYİ - Thread pool
ExecutorService executor = Executors.newFixedThreadPool(4);
// ❌ KÖTÜ - Manuel thread oluşturma
for (int i = 0; i < 100; i++) {
new Thread(task).start(); // Resource waste
}
- Thread extend etmek yerine Runnable implement edin
- Lambda expressions kullanın (modern ve temiz)
- join() ile thread'lerin bitmesini bekleyin
- Daemon thread'leri background işler için kullanın
- Thread priority'ye güvenmeyin (OS dependent)
// ✅ İYİ - Thread-safe
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();
// ❌ KÖTÜ - Race condition
int counter = 0;
counter++; // Not thread-safe
// ✅ İYİ - ReentrantLock (özellikle Virtual Threads ile)
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// critical section
} finally {
lock.unlock();
}
// ⚠️ DIKKAT - Synchronized (Virtual Thread pinning yapabilir)
synchronized(this) {
// critical section - can pin virtual threads
}
- AtomicInteger, AtomicBoolean gibi atomic classes tercih edin
- ConcurrentHashMap kullanın, synchronized Map yerine
- ReentrantLock kullanın, synchronized yerine (özellikle Virtual Threads)
- Lock timeout kullanın (tryLock with timeout)
- Nested synchronized blocks'tan kaçının
- Synchronized block'ları küçük tutun
- Lock sırasını tutarlı tutun (deadlock prevention)
// ✅ İYİ - Consistent lock ordering
private final Object lock1 = new Object();
private final Object lock2 = new Object();
// Always acquire locks in same order
synchronized(lock1) {
synchronized(lock2) {
// safe
}
}
// ✅ CPU-bound için
ExecutorService cpuExecutor = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()
);
// ✅ I/O-bound için
ExecutorService ioExecutor = Executors.newCachedThreadPool();
// veya
ExecutorService ioExecutor = Executors.newFixedThreadPool(50);
- newFixedThreadPool CPU-bound task'lar için
- newCachedThreadPool I/O-bound task'lar için
- CompletableFuture async chain'ler için
- Always shutdown executors properly (try-with-resources)
- Handle RejectedExecutionException
- Custom ThreadFactory kullanın naming için
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
// ✅ İYİ
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
// ❌ KÖTÜ
Map<String, Integer> map = Collections.synchronizedMap(new HashMap<>());
Semaphore connectionPool = new Semaphore(3); // Max 3 connections
connectionPool.acquire();
try {
// use resource
} finally {
connectionPool.release();
}
CountDownLatch latch = new CountDownLatch(3);
// Workers countdown when done
latch.await(); // Main waits for all
- ConcurrentHashMap > synchronized Map
- BlockingQueue producer-consumer için
- Semaphore resource limiting için
- ReadWriteLock read-heavy scenarios için
- Lock'ları finally block'ta unlock edin
// ✅ İYİ - Large dataset + CPU-bound
List<Integer> largeList = IntStream.rangeClosed(1, 1_000_000).boxed().toList();
long sum = largeList.parallelStream()
.mapToLong(i -> heavyComputation(i))
.sum();
// ❌ KÖTÜ - Small dataset
List<Integer> smallList = Arrays.asList(1, 2, 3, 4, 5);
smallList.parallelStream() // Overhead > benefit
.mapToInt(i -> i * i)
.sum();
- Large dataset'ler için kullanın (>1000 elements)
- CPU-bound operations için ideal
- ArrayList > LinkedList (spliterator efficiency)
- Custom ForkJoinPool I/O operations için
- Thread-safe operations kullanın
- I/O operations için async chains
- Exception handling ile exceptionally()
- Multiple futures için thenCombine()
- Custom executor kullanın task type'a göre
- Timeout ekleyin get(timeout)
- Milyonlarca thread daha az memory ile
- JVM-managed (carrier thread'ler üzerinde)
- Perfect I/O-bound operations için
- Platform thread'lerin 1000x daha az memory
// ✅ İYİ - Virtual thread creation
Thread virtualThread = Thread.ofVirtual().start(() -> {
// I/O-bound task
});
// ✅ İYİ - Virtual thread executor
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
// Submit I/O tasks
}
// ✅ İYİ - Thread factory
ThreadFactory factory = Thread.ofVirtual().factory();
// ❌ KÖTÜ - Pinning causes (synchronized)
synchronized(lock) {
Thread.sleep(1000); // Pins virtual thread to carrier
}
// ❌ KÖTÜ - JNI calls
nativeMethod(); // Can cause pinning
// ✅ İYİ - ReentrantLock (no pinning)
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
Thread.sleep(1000); // Virtual thread can be unmounted
} finally {
lock.unlock();
}
- I/O-bound tasks için kullanın (HTTP calls, DB operations)
- Executors.newVirtualThreadPerTaskExecutor() kullanın
- ReentrantLock kullanın, synchronized yerine
- try-with-resources executor management için
- Structured concurrency pattern'ini uygulayın
- CPU-intensive tasks için kullanmayın
- synchronized blocks kullanmayın (pinning risk)
- Virtual thread'leri pool etmeyin
- Excessive ThreadLocal kullanmayın (memory risk)
- JNI calls'tan kaçının
Platform Thread: ~1-2MB per thread
Virtual Thread: ~Few KB per thread
Scalability: Thousands vs Millions
Management: OS vs JVM
- ✅ CPU-bound computations
- ✅ Long-running background services
- ✅ Limited concurrency scenarios
- ✅ Legacy system integrations
- ✅ HTTP client calls
- ✅ Database operations
- ✅ File I/O operations
- ✅ Web servers (request per thread)
- ✅ Microservice communications
- ✅ Bounded resource scenarios
- ✅ CPU-intensive parallel processing
- ✅ Task batching requirements
- ✅ Complex lifecycle management
- AtomicInteger > int++ (race conditions)
- ConcurrentHashMap > synchronized HashMap
- Immutable objects tercih edin
- Defensive copying yapın
- Always shutdown executors
- Use try-with-resources
- Handle InterruptedException properly
- Avoid resource leaks
- ReentrantLock > synchronized
- Avoid pinning operations
- Don't pool virtual threads
- Minimize ThreadLocal usage
- Right tool for right job
- Measure before optimizing
- Consider data locality
- Profile thread usage
- Use meaningful thread names
- Log thread information
- Monitor thread pools
- Use JVM monitoring tools
1. Virtual Threads (Java 21+) - for I/O-bound
2. CompletableFuture - for async chains
3. ExecutorService - for managed concurrency
4. Direct Thread - only for simple cases
1. Concurrent Collections (ConcurrentHashMap)
2. Atomic Classes (AtomicInteger)
3. ReentrantLock (especially with Virtual Threads)
4. synchronized (last resort, avoid with Virtual Threads)
❌ Creating threads manually instead of using executors
❌ Using synchronized with Virtual Threads
❌ Not handling InterruptedException
❌ Forgetting to shutdown executors
❌ Using parallel streams for small datasets
❌ Race conditions with shared mutable state
❌ Deadlocks with inconsistent lock ordering
❌ ThreadLocal abuse with Virtual Threads
Java threading evrimi:
- Java 1.0: Basic threads
- Java 5: Executor Framework
- Java 8: Parallel Streams, CompletableFuture
- Java 21: Virtual Threads (Project Loom)
Modern Java'da threading yaklaşımı:
- Virtual Threads I/O-bound tasks için
- Platform Threads + Executors CPU-bound tasks için
- Concurrent Collections thread-safe data structures için
- CompletableFuture async programming için