---
title: 'Sanal İş Parçacıkları'
source: 'https://academia.sh/tr/kurslar/jvm-ve-eszamanlilik/sanal-is-parcaciklari'
course: 'JVM, Eşzamanlılık ve Başarım'
language: tr
updated: '2026-08-23T07:00:56+00:00'
license: 'CC BY-SA 4.0'
---

# Sanal İş Parçacıkları

Aynı görev kümesinin sabit havuzda dörtle sınırlı kalan, sanal iş parçacığı başına bir yürütücüde görev sayısı kadar büyüyen ayrık kimliği; sanal iş parçacığının isVirtual() ile ayırt edilen gerçek bir Thread olup platform iş parçacığı olmadığı; taşıyıcının gözlemlenemediği; ThreadLocal'ın sanal iş parçacığı başına ayrı bir değer tutmasının bir bedel olduğu ölçülür.

Önceki ders havuzun iş parçacıklarını sınırlı bir kaynak olarak ele aldı: sabit sayıda
vardılar, doldular, sırayla yeniden kullanıldılar, doluluk çağıranı işe koştu. Bu sınır,
platform iş parçacığının kendisinin pahalı olmasından geliyordu — her biri işletim
sisteminin ayırdığı bir yığıt ve bir çizelgeleme kaydı taşıyor. Bu ders aynı soruyu bambaşka
bir öncülle soruyor: ya iş parçacığının kendisi ucuzsa? Sanal iş parçacığı, işletim
sisteminden değil sanal makineden gelen, çok daha ucuz bir iş parçacığı türü. Aynı görev
kümesi iki ayrı yürütücüde koşturuluyor ve aynı soru yeniden soruluyor — bu adımı hangi iş
parçacığı koşturuyor — ama bu kez yanıtın **kaç ayrı** iş parçacığı üretebileceği de ölçülüyor.

Önceki derste yürütücü arayüzü sabit kalmıştı, değişen yalnızca kurucuya verilen sayıydı.
Bu derste arayüz yine aynı — `ExecutorService`, `submit`, `invokeAll` — ama yürütücünün
kendisi bambaşka bir kaynaktan iş parçacığı üretiyor. Aynı arayüzün arkasında iki ayrı
üretim düzeneği durabiliyor olması, bu dersin ölçtüğü ilk şey.

## Aynı Görev Kümesi İki Yürütücüde: Ayrık Kimlik Sayısı

Önceki dersin ayrık kimlik sayma tekniği aynen sürüyor: her görev koştuğu iş parçacığını
yalnızca kimliğiyle (`==` eşdeğeri) bir kümeye ekliyor, ad ya da numara basılmıyor.

**EZ22.** Ayrık kimlik sayımı sanal iş parçacıkları için de geçerlidir; `Thread` sınıfının
bir alt tipi olarak sanal iş parçacığı da bir nesne kimliği taşır ve aynı kümeyle sayılabilir.
**EZ23.** Sanal iş parçacığı başına bir yürütücü (`newVirtualThreadPerTaskExecutor`), kendisine
verilen her görev için **yeni bir** sanal iş parçacığı oluşturur; bu, yürütücünün yapısal bir
kuralıdır ve çizelgelemeye bağlı değildir.

```java
// Kimlikler.java — ayrik is parcacigi kimligini == esdegeriyle sayan kume (yardimci kaynak, main yok)
class Kimlikler {
    static java.util.Set<Thread> yeniKume() {
        return java.util.Collections.synchronizedSet(
                java.util.Collections.newSetFromMap(new java.util.IdentityHashMap<>()));
    }
}
```

```java
// IkiYurutucu.java — ayni gorev kumesi sabit havuzda ve sanal yurutucude ayri ayrik kimlik sayisi uretiyor
import java.util.*;
import java.util.concurrent.*;

public class IkiYurutucu {
    static int ayrikKimlik(ExecutorService yurutucu, int gorevSayisi) throws Exception {
        Set<Thread> gorulen = Kimlikler.yeniKume();
        List<Callable<Void>> gorevler = new ArrayList<>();
        for (int i = 0; i < gorevSayisi; i++) {
            gorevler.add(() -> { gorulen.add(Thread.currentThread()); return null; });
        }
        yurutucu.invokeAll(gorevler);
        return gorulen.size();
    }

    public static void main(String[] args) throws Exception {
        try (ExecutorService sabit4 = Executors.newFixedThreadPool(4);
             ExecutorService sanal = Executors.newVirtualThreadPerTaskExecutor()) {
            System.out.println("4 gorev,  sabit havuz(4)   -> ayrik kimlik: " + ayrikKimlik(sabit4, 4));
            System.out.println("4 gorev,  sanal yurutucu   -> ayrik kimlik: " + ayrikKimlik(sanal, 4));
        }
        try (ExecutorService sabit4b = Executors.newFixedThreadPool(4);
             ExecutorService sanalb = Executors.newVirtualThreadPerTaskExecutor()) {
            System.out.println("20 gorev, sabit havuz(4)   -> ayrik kimlik: " + ayrikKimlik(sabit4b, 20));
            System.out.println("20 gorev, sanal yurutucu   -> ayrik kimlik: " + ayrikKimlik(sanalb, 20));
        }
    }
}
```

```
4 gorev,  sabit havuz(4)   -> ayrik kimlik: 4
4 gorev,  sanal yurutucu   -> ayrik kimlik: 4
20 gorev, sabit havuz(4)   -> ayrik kimlik: 4
20 gorev, sanal yurutucu   -> ayrik kimlik: 20
```

Dört görevle iki yürütücü de dört ayrı kimlik üretiyor — görev sayısı havuz büyüklüğüne eşit
olduğunda fark görünmüyor. Yirmi görevde fark açılıyor: sabit havuz **yine** dört ayrı
kimlikte kalıyor, çünkü önceki dersteki kural burada da geçerli — havuz büyüklüğü kaç görev
geldiğinden bağımsız bir üst sınır. Sanal yürütücü ise tam **yirmi** ayrı kimlik üretiyor;
her görev kendi sanal iş parçacığını buluyor, hiçbiri bir öncekini bekleyip onun yerini
almıyor. Sayı görev sayısıyla birlikte büyüyor, çünkü sanal iş parçacığı yürütücüsü bir
havuz değil — her görev için yeni bir iş parçacığı **doğuran** bir düzenek (EZ23).

Yirmi sayısı rastgele seçilmedi: dört, havuz büyüklüğüyle örtüştüğü için farkı göstermiyor;
yirmi, havuz büyüklüğünün birkaç katı olduğu için sabit havuzun **tavanına** çarpıp orada
kaldığını görünür kılıyor. Önceki derste bu tavan bir dolulukla, `execute` çağrısının
çağırana düşmesiyle ölçülmüştü; burada aynı tavan bir üretim sınırı olarak, ayrık kimlik
sayısı üzerinden ölçülüyor.

## Sanal İş Parçacığı da Bir İş Parçacığıdır

Ayrık kimliği büyüten şeyin gerçekten bir iş parçacığı olup olmadığı ayrıca doğrulanabilir.

**EZ24.** Bir iş parçacığının sanal olup olmadığı yalnızca `Thread.isVirtual()` ile ayırt
edilir; ad, öncelik ya da başka hiçbir gözlemle değil.

```java
// IsVirtualKontrolu.java — sanal is parcacigi Thread.isVirtual() ile ayirt edilen gercek bir Thread'dir
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;

public class IsVirtualKontrolu {
    static int sanalSayisi(ExecutorService yurutucu, int gorevSayisi) throws Exception {
        AtomicInteger sanal = new AtomicInteger(0);
        List<Callable<Void>> gorevler = new ArrayList<>();
        for (int i = 0; i < gorevSayisi; i++) {
            gorevler.add(() -> {
                if (Thread.currentThread().isVirtual()) sanal.incrementAndGet();
                return null;
            });
        }
        yurutucu.invokeAll(gorevler);
        return sanal.get();
    }

    public static void main(String[] args) throws Exception {
        try (ExecutorService sabit4 = Executors.newFixedThreadPool(4)) {
            System.out.println("sabit havuz(4), 4 gorev  -> sanal is parcaciginda kosan: "
                    + sanalSayisi(sabit4, 4) + "/4");
        }
        try (ExecutorService sanal = Executors.newVirtualThreadPerTaskExecutor()) {
            System.out.println("sanal yurutucu, 4 gorev  -> sanal is parcaciginda kosan: "
                    + sanalSayisi(sanal, 4) + "/4");
        }
        Thread anaIsParcacigi = Thread.currentThread();
        System.out.println("ana is parcacigi sanal mi                                : "
                + anaIsParcacigi.isVirtual());
    }
}
```

```
sabit havuz(4), 4 gorev  -> sanal is parcaciginda kosan: 0/4
sanal yurutucu, 4 gorev  -> sanal is parcaciginda kosan: 4/4
ana is parcacigi sanal mi                                : false
```

Sabit havuzun dört görevinin hiçbiri sanal iş parçacığında koşmuyor, sanal yürütücünün
dördü de koşuyor; kodu koşturan görev **aynı** lambda, değişen yalnızca yürütücü. `main`
metodunu koşturan iş parçacık da sanal değil — programın kendisi her zaman bir platform iş
parçacığından başlıyor. Sanal iş parçacığı `Thread` sınıfının bir alt tipi olarak var
oluyor: aynı `start`, `join`, `interrupt` çağrılarını, aynı yaşam döngüsünü paylaşıyor, ama
**platform iş parçacığı değil** — yani işletim sisteminin ayırdığı bir iş parçacığına
birebir karşılık gelmiyor. Bu ayrımın önceki bölümdeki sayıyla ilişkisi doğrudan: sanal
yürütücü yirmi ayrı kimlik üretebiliyordu çünkü ürettiği şey işletim sisteminin ayırdığı bir
kaynak değil, sanal makinenin kendi yönettiği bir nesne. `isVirtual()` bu iki türü
ayırmanın tek yolu; ikisi de aynı üst sınıftan geldiği ve aynı yöntemleri barındırdığı için
başka hiçbir çağrı bu ayrımı vermiyor.

## Engellenen Sanal İş Parçacığı Taşıyıcısından İner

Bir platform iş parçacığı yoksa sanal iş parçacığının kodu nasıl koşuyor? Sanal iş
parçacığı, koşarken bir **taşıyıcı iş parçacığına** biniyor — kod aslında bir platform iş
parçacığı üzerinde çalışıyor, sanal iş parçacığı o platform iş parçacığını ödünç alıyor.
Sanal iş parçacığı `CountDownLatch.await()` gibi bir çağrıda **engellenirse**, taşıyıcısından
**iniyor**: altındaki platform iş parçacığı serbest kalıyor ve başka bir sanal iş
parçacığının kodunu koşturabiliyor. Engelleme bitince sanal iş parçacığı yeniden bir
taşıyıcıya biniyor — zorunlu olarak aynısına değil.

Binme ve inme, `CountDownLatch`, kilitler ve engelleyen girdi-çıktı gibi standart
kütüphane çağrılarında düzenek tarafından yönetiliyor; çağıran bu düzeneği kurmuyor, yalnız
her zamanki gibi `await()` ya da `get()` çağırıyor. Bir sanal iş parçacığının kaç kere binip
indiği de gözlemlenemeyen bir şey — bu ders o sayıyı da ölçmüyor, yalnızca kaç ayrı sanal iş
parçacığı kimliğinin **aynı anda** var olabildiğini ölçüyor.

**Taşıyıcı iş parçacığı gözlemlenemez** — bu ders onu ne yazdırıyor ne de sayıyor; ölçülen
şey yalnızca ayrık **sanal** iş parçacığı kimliği sayısı. **EZ25.** Elli görev, hepsi bir
`CountDownLatch` üzerinde engellenip aynı anda serbest bırakılacak biçimde kuruluyor;
serbest bırakma çağrısı görevlerin tamamı gönderildikten hemen sonra yapılıyor, bu yüzden
gecikme bir yarış değil — `CountDownLatch` sıfıra indiğinde bekleyen her görev, ne zaman
kontrol ederse etsin aynı yanıtı görüyor.

```java
// EngellenenGorevler.java — elli engelleyen gorev sabit havuzda dort, sanal yurutucude elli ayrik kimlik uretiyor
import java.util.*;
import java.util.concurrent.*;

public class EngellenenGorevler {
    static int engellenerekAyrikKimlik(ExecutorService yurutucu, int gorevSayisi) throws Exception {
        Set<Thread> gorulen = Kimlikler.yeniKume();
        CountDownLatch birak = new CountDownLatch(1);
        List<Future<?>> gelecekler = new ArrayList<>();
        for (int i = 0; i < gorevSayisi; i++) {
            gelecekler.add(yurutucu.submit(() -> {
                try {
                    birak.await();
                } catch (InterruptedException e) { }
                gorulen.add(Thread.currentThread());
            }));
        }
        birak.countDown();
        for (Future<?> g : gelecekler) g.get();
        return gorulen.size();
    }

    public static void main(String[] args) throws Exception {
        int gorevSayisi = 50;
        try (ExecutorService sabit4 = Executors.newFixedThreadPool(4)) {
            System.out.println(gorevSayisi + " engelleyen gorev, sabit havuz(4) -> ayrik kimlik: "
                    + engellenerekAyrikKimlik(sabit4, gorevSayisi));
        }
        try (ExecutorService sanal = Executors.newVirtualThreadPerTaskExecutor()) {
            System.out.println(gorevSayisi + " engelleyen gorev, sanal yurutucu -> ayrik kimlik: "
                    + engellenerekAyrikKimlik(sanal, gorevSayisi));
        }
    }
}
```

```
50 engelleyen gorev, sabit havuz(4) -> ayrik kimlik: 4
50 engelleyen gorev, sanal yurutucu -> ayrik kimlik: 50
```

Elli görev de bir kilit noktasında engelleniyor, sonra hep birlikte serbest bırakılıyor.
Sabit havuzda dört platform iş parçacığı bu elli görevi sırayla, dördü bir kerede işliyor —
ayrık kimlik dörtte kalıyor. Sanal yürütücüde ellisi de kendi sanal iş parçacığını buluyor
ve hepsi tamamlanıyor; hiçbiri "iş parçacığı kalmadı" diye kuyrukta beklemiyor. Bu, dört
platform iş parçacığıyla kurulu bir taşıyıcı havuzunun elli sanal iş parçacığını
karşılayabildiğini gösteriyor — engellenen bir sanal iş parçacığı taşıyıcısını serbest
bıraktığı için. **Sınırlayıcı ölçüm:** ayrık kimlik sayısının büyümesi bir hız iddiası
değil, bir olgudur. Bu derste hiçbir süre ölçülmüyor; ölçülen tek şey kaç ayrı sanal iş
parçacığı kimliğinin var olabildiği. Bunun bir kazanca dönüşüp dönüşmediği ancak engelleyen
kodun kaç iş parçacığını meşgul ettiği sorusuyla anlamlıdır, ve o soru burada
**modelleniyor**, koşturulmuyor: elli görevin hepsi platform iş parçacığı sayısını aşan bir
"aynı anda engellenmiş görev" sayısına ulaşabildi, oysa sabit havuzda böyle bir sayı hiçbir
zaman dörtten büyük olamazdı.

Bu modelleme kasıtlı bir sınır çiziyor: elli sayısını yüz ya da bin yapmak sonucu
değiştirmez, yalnızca aynı olguyu daha büyük bir sayıyla tekrarlar. Ders burada durup
büyütmüyor, çünkü iddia zaten sayının kendisinde değil, sabit havuzun **asla** aşamadığı bir
tavanın sanal yürütücüde hiç var olmamasında. Bir platform iş parçacığı engellendiğinde
kendi yığıtını ve çizelgeleme kaydını tutmaya devam eder; sanal iş parçacığı engellendiğinde
altındaki platform iş parçacığını bırakır ve geriye yalnızca sanal makinenin tuttuğu küçük
bir durum kalır — taşıyıcının kaç kez el değiştirdiği bu derste sayılmayan, ama yukarıdaki
"elli tamamlandı" sonucunun arkasında duran şey.

## ThreadLocal Görev Başına Ayrı Değer Tutar: Bir Bedel

Sanal iş parçacığının bu bolluğunun bir bedeli var. `ThreadLocal`, her iş parçacığına kendi
değerini veren bir yapı; değer ilk `get()` çağrısında `initialValue` ile üretiliyor ve o iş
parçacığına bağlı kalıyor.

```java
// OzelDegerBedeli.java — ThreadLocal her sanal is parcacigina ayri bir deger uretiyor, sabit havuzda paylasiliyor
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;

public class OzelDegerBedeli {
    static int uretilenDegerSayisi(ExecutorService yurutucu, int gorevSayisi) throws Exception {
        AtomicInteger sayac = new AtomicInteger(0);
        ThreadLocal<Integer> ozel = ThreadLocal.withInitial(sayac::incrementAndGet);
        List<Callable<Void>> gorevler = new ArrayList<>();
        for (int i = 0; i < gorevSayisi; i++) {
            gorevler.add(() -> { ozel.get(); return null; });
        }
        yurutucu.invokeAll(gorevler);
        return sayac.get();
    }

    public static void main(String[] args) throws Exception {
        int gorevSayisi = 10;
        try (ExecutorService sabit4 = Executors.newFixedThreadPool(4)) {
            System.out.println(gorevSayisi + " gorev, sabit havuz(4) -> uretilen ozel deger: "
                    + uretilenDegerSayisi(sabit4, gorevSayisi));
        }
        try (ExecutorService sanal = Executors.newVirtualThreadPerTaskExecutor()) {
            System.out.println(gorevSayisi + " gorev, sanal yurutucu -> uretilen ozel deger: "
                    + uretilenDegerSayisi(sanal, gorevSayisi));
        }
    }
}
```

```
10 gorev, sabit havuz(4) -> uretilen ozel deger: 4
10 gorev, sanal yurutucu -> uretilen ozel deger: 10
```

`ozel` değişkeni tek bir `ThreadLocal` nesnesi, ikisinde de aynı kod satırından okunuyor.
Sabit havuzda on görev dört platform iş parçacığını paylaşıyor; her platform iş parçacığı
kendi değerini bir kez üretip sonraki görevlerde onu **yeniden kullanıyor** — toplam dört
değer üretiliyor. Sanal yürütücüde on görevin her biri kendi sanal iş parçacığında koştuğu
için `initialValue` on kez çağrılıyor, hiçbir değer paylaşılmıyor. Bu, önceki bölümün
ayrık kimlik sayısıyla aynı yönde bir sonuç ama tersine okunması gereken bir sonuç: sanal iş
parçacığının bol olması, `ThreadLocal` kullanan koda o bolluk kadar üretim yaptırıyor.
Görev başına bir sanal iş parçacığı çalıştırmak, görev başına bir `ThreadLocal` değeri
kurmak demek olabiliyor — bu bir bedel, ve büyük görev sayılarında gözden kaçırılabilir.

Dört ölçüm birlikte okununca kurs şu iddiaya varıyor: sanal iş parçacığı, iş parçacığının
kendisini ucuzlatıyor ama iş parçacığıyla **birlikte gelen** her şeyi ucuzlatmıyor.
`ThreadLocal` bunun ölçülen tek örneği; iş parçacığı sayısına bağlı büyüyen başka her yapı
aynı bedeli taşıyabilir. Ayrık kimlik sayısı büyüdükçe bu bedel de büyür — kazancın kendisi
gibi, bedel de bir olgudur ve ayrı ölçülmesi gerekir.

## Özet

- Aynı görev kümesi sabit havuzda havuz büyüklüğüyle sınırlı kalan, sanal iş parçacığı
  başına bir yürütücüde görev sayısı kadar büyüyen bir ayrık kimlik sayısı üretir.
- Sanal iş parçacığı `Thread.isVirtual()` ile ayırt edilen gerçek bir `Thread`'dir; aynı
  yaşam döngüsü API'sini paylaşır ama bir platform iş parçacığına birebir karşılık gelmez.
- Engellenen bir sanal iş parçacığı taşıyıcısından iner ve platform iş parçacığını serbest
  bırakır; taşıyıcı gözlemlenemez, ölçülen şey yalnızca ayrık sanal kimlik sayısıdır.
- Ayrık kimlik sayısının büyümesi bir hız iddiası değildir, bir olgudur; kazanç ancak
  engelleyen kodun kaç iş parçacığını meşgul ettiği sorusuyla anlamlıdır ve bu soru
  modellenir, koşturulmaz.
- `ThreadLocal`, sabit havuzda değeri iş parçacıkları arasında paylaştırırken sanal
  yürütücüde görev başına ayrı bir değer üretir; bu, sanal iş parçacığının bolluğunun
  taşıdığı bir bedeldir.

## Sonraki Adım

Bu ders iş parçacığının kendisini ucuzlattı ve görev başına bir tane doğurmanın mümkün
olduğunu gösterdi. Ama şimdiye kadarki bütün görevler bağımsızdı — biri bitmeden öteki
başlamıyordu. Sıradaki ders bağımsız görevleri bir **zincire** diziyor: bir adımın sonucu
bir sonrakinin girdisi oluyor. Zincire dizilen bu aşamaları kim koşturuyor sorusu, artık
tek bir görev değil ardışık bir dizi için soruluyor.
