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