JAVA MULTITHREADING — Bolum -1: Threads, Process, Thread Pooling ve Executors

Ibrahim Halil Koyuncu
6 min readMay 5, 2022

--

Merhaba, bu yazı serisinde Thread yapısı, Multithreading ve Concurrent programming, Locking Mekanizmaları ve race condition, Inter-Thread communication, Semaphore, Producer-Consumer pattern, Lock-Free data types gibi birçok konudan bahsediyor olacağız.

İlk olarak aşağıdaki kavramları inceleyerek ve sorulara cevap arayarak başlayalım.

  • Thread nedir ve nasıl oluşturulur ? Thread Scheduling mantığı nasıldır ?
  • Thread vs Process? Process yapısına genel bir bakış
  • Neden multi-thread yapısına ihtiyacımız var ? Single thread vs Multithread
  • Daemon thread nedir ve Thread class’ına ait temel methodlar nelerdir?
  • Thread pooling ve executers kavramları nelerdir ?

Thread nedir ve nasıl oluşturulur ? Thread Scheduling mantığı nasıldır ?

Thread kelimesi Türkçe’ye iş parçası veya işi yapan parça olarak çevrilebilir. Bir uygulamamız(process) en az bir tane thread’e(işi yapan parçaya) sahip olmalıdır. Bu thread “Main Thread” olarak adlandırılır. Main thread yanında ilgili akışa göre başka threadler oluşturulup verilen task tamamlandığında ise thread sonlandırılır.

Javada Thread Class’ın constructur’ına; Runnable interface’ini uygulayan herhangi bir class’ın objesini geçtiğimizde thread objemiz oluşmuş olacaktır. “run” methodu içinde yazılan kod thread çalıştığında execute edilecektir.Yeni bir thread’i başlatmak için sadece thread objesini oluşturmak yeterli değildir. Ayrıca thread’i başlatmak için “threadObject.start()” methodunuda call etmemiz gerekecektir.

Bir diğer yöntem ise Thread Class’ından miras alarak oluşturduğumuz class’ta; yine run methodunu override ederek doğrudan bu class’tan obje yaratarak thread’i start edebiliriz.

Yukarıdaki örneklerde nasıl thread oluşturup başlatacağımızdan bahsettik şimdi ise thread scheduling kısmına değinelim. İşletim sistemi zamanı belirli parçalara böler ve her bir parça “Epoch” olarak adlandırılır. Her bir epochta işletim sistemi her bir thread için farklı zaman dilimleri tahsis eder. Her epochta tüm threadler çalışmak veya bitmek zorunda değildir bunun yerine epochlar üzerinde yayılarak atanan işi tamamlamaya çalışırlar.

Thread Scheduling — Time Slices

Her bir thread’e atanan zaman ise Dynamic Priority hesaplamasına dayalıdır.

Dynamic Priority = Static Priority + C

“Static priority” değişkeni developer tarafından belirlenir ve atanır. “C” ise OS(Operating System) tarafından her bir Epoch’da çalışacak threadler için belirlenir. Bu yolla OS thread’leri nasıl schedule edileceğini belirler.

Thread vs Process — Process Yapısına Genel Bir Bakış

Bilgisayarda herhangi bir uygulamayı çalıştırdığımızda işletim sistemi programa ait ilgili dosyaları okur ve uygulamanın bir instance’ını yaratarak memory’e yükler. Bu instance “Process” olarak veya “Context of application” olarak adlandırılır. Her bir process diğer processlerden tamamen izole bir yapıdadır.

Process ile thread arasındaki fark ise; process çalışan uygulamanın kendisi iken; thread ise process içinde çalışan bir iş parçacığıdır.

Process Structure
  • Process bazı metadata bilgileri içerir; örneğin processId, name, startTime, user gibi parametrelerdir.
  • Files : Process(uygulama) tarafından read/write operasyonları için açılan dosyalardır.
  • Code : CPU üzerinde execute edilecek program talimatlarını içerir.
  • Heap : Processe ait paylaşılan memory alanıdır. Bütün threadler heap üzerindeki datayı paylaşır, herhangi bir anda heap’de tutulan objeleri okuyabilir veya heap üzerinde yeni obje oluşturabilir. Java’da tüm objeler, static değişkenler ve class member’ları heap üzerinde tutulur bu yüzden tüm thread’ler bu datalara erişebilir.
  • Stack : Methodların çağrıldığı, method parametrelerinin kaydedildiği ve bütün local değişkenlerin tutulduğu memory alanıdır. Her thread’in kendine ait izole stack alanı vardır ve diğer thread’ler bu alana erişemez. Thread oluşturulduğunda sabit boyutta stack alanıda doğrudan tahsis edilir.(Büyüklük platforma göre değişir) Instruction hiyerarşisi(frame sayısı) çok fazla ise bazen StackOverflow exception alma olasılığı yükselecektir bu yüzden özellikle recursive fonksiyon kullanımlarında dikkat edilmelidir.
Stack Structure with example

Yukarıdaki şekilde de görüleceği üzere her bir method bir frame’e denk gelmektedir ve her method kendi frame’indeki değişkenleri görebilir. Sum fonksiyonu değeri return ettikten sonra stack’den kaldırılır ve değer main method frame’inde result değişkenine atanır ve başka instruction olmadığından main method’da stack’den kaldırılır.

  • Instruction Pointer : Execute edilecek bir sonraki instruction’ın adresi

Stack + Instruction Pointer = Her bir thread’in execution durumunu gösterir.

Neden Multithread Yapısına İhtiyacımız Vardır ? Single Thread vs Multi Thread

Uygulamamızda cevaplanabilirlik(responsiveness) ve performansı arttırmak istediğimiz zaman multithread yapısına gitmeyi tercih edebiliriz.

Örneğin single thread çalışan ve binlerce kullanıcısı olan uygulamamız olduğunu düşünelim. Bir kullanıcı uzun bir operasyon gerektiren bir istekte bulundu. Aynı anda başka bir kullanıcıda bir istekte bulunduğunda ilk istekte bulunan kullanıcının işlemleri tamamlanana kadar cevap alamayacak ve beklemek zorunda kalacaktır. Eğer multithread yapısını kullanırsak birçok kullanıcıyı anlık servis sağlayarak responsiveness’ı arttırabiliriz. Uygulama her bir isteğe karşılık bir thread oluşturarak tüm kullanıcılara anlık cevap dönebilir hale gelecektir.

Performans anlamında ise yine belirli bir işi parçalayarak ayrı thread’lere atayıp paralel olarak işlem başlattığımızda yine uygulamamız single thread kullanıldığında sequencial ilerleyen haline göre çok daha performanslı çalışacaktır. Performansı örnek olarak iki kategoride ölçerek kıyaslama yapabiliriz. Bunlar latency ve throughput’tur. Latency; bir işin tamamlanma süresidir. Throughput ise verilen periyotta tamamlanan iş miktarıdır ve tasks/time olarak ölçülebilir. Bu kriterlere göre single thread ve multi thread yapıları arasında performans ölçümleri yapabiliriz.

Bu gibi avantajların dışında dezavantajlarından da bahsedecek olursak; daha fazla CPU ve memory consume edilecektir ve multi thread yapısı race condition gözetilerek kurulmazsa doğru sonuçlar alınamayacaktır.

Daemon Thread Nedir ve Thread Class’ına Ait Temel Methodlar Nelerdir ?

Bir threadi Daemon olarak tanımladığımız zaman uygulamanın sonlanması için engel oluşturmayan bir thread tanımladığımızı ifade ederiz. Process içinde en az bir thread çalıştıkça(main thread terminate olsa bile) ilgili process durdurulamayacaktır ta ki tüm threadler terminate olana kadar. Bu yüzden bazı senaryolarda bazı thread’leri Daemon Thread olarak tanımlamamız gerekebilir. Örneğin bir text editör uygulamasında uygulamayı kapattığımızda verinin kaydedilmesini beklemek istemeyiz. Bu tür operasyonlar için daemon thread kullanarak uygulamanın kapatılmasını bloke etmemiş oluruz.

Thread Class’ına ait thread oluştururken kullanabileceğimiz bazı temel methodlar aşağıdaki gibidir.

  • setName : Oluşturulan threade isim atamak için kullanılır.
  • setDaemon : Daemon thread olup olmadığını belirtmek için kullanılır.
  • setPriority : Thread’e priorityi atamak için kullanılır. Yukarıda bahsettiğimiz static priority’e karşılık gelir.
  • setUncoughtExceptionHandler : Thread uncoughtException sebebi ile terminate edilmek istendiğinde JVM ilgili handlerı sorgular ve varsa ilgili methodu çağırır.
  • currentThread : O an çalışan thread objesini geriye döndürür.
  • sleep : O an çalışan threadin uykuya geçmesini sağlar.
  • start : Threadin başlatılmasından sorumludur.
  • interrupt : İlgili threadin çalışmasını durdurur.
  • isInterrupted : Thread interrupt edildiyse True değilse False döner.
  • join : İlgili thread sonlanana kadar bekletir.

Thread Pool ve Executors

Multithread bir uygulamada sürekli olarak thread yaratmak bazen performansı arttırmak yerine düşürebilmektedir çünkü yeni bir Thread yaratmak OS için masraflı bir iştir. Çünkü yeni bir Thread’in yaratılması, Thread için gerekli kaynakların ayrılması, oluşturulan her thredi yönetmek ve bunlar arasında geçişler yapmak bazen ciddi maliyet artışlarına sebep olmaktadır. Bu yüzden Thread Pool oluşturarak thread sayısını kontrol altında tutabilir ve sürekli olarak her yeni iş için yeni bir thread oluşturmak yerine pool’dan bir threade atayarak ilgili işi yaptırabiliriz.

Thread pool oluşturmak için Executor Framework’ü kullanabiliriz. Executor class’ından dört çeşit thread pool oluşturabiliriz.

  • newFixedThreadPool
  • newCachedThreadPool
  • newSingleThreadedExecutor
  • newScheduledThreadPool

Fixed Thread Pool :

Fixed thread pool oluşturduğumuzda havuzumuzda sabit sayıda thread olacaktır. Tasklar executor’a submit edildikten sonra her biri bir thread’e atanarak işlem başlar. Task sayısı thread sayısından fazla ise tasklar queue’da tutular ve işi biten thread queue’dan yeni taskı thread safe şekilde alır(Atomic). Yani bir taskı aynı anda 2 thread queue’dan consume edemez. Bazı temel executor service methodları aşağıdaki gibidir.

  • submit : Executor’a task göndermek için kullanılır.
  • shuwDown : Yeni işlem alımını durdurur ve mevcut işlemlerin bitirilmesini sağlar.
  • awaitTermination: Mevcut işlemlerin bitirilmesi için belirli bir süre tanır ve bu sürenin sonunda ExecutorService tamamen kapatılır.

— Cached Thread Pool :

Cached Thread Pool sabit thread sayısına sahip bir yapıda değildir gereksinime göre bu sayı artabilir. Kısa süreli tasklar içeren uygulamalar için kullanılması uygundur. Eğer cached thread pool’a yeni bir task gelirse ve tüm varolan thread’ler meşgulse; cached thread pool submit edilen task için yeni bir thread create edecektir. Yeni oluşturulan thread’e 1 dakika içinde yeni bir task submit edilmezse executor service bu threadi kill edecektir.

Single Thread Executor:

Single thread executor havuzunda sadece bir thread vardır. Tüm tasklar blocking queue’da tutulur. Her bir taskın bitiminde thread queue’dan yeni bir task alır ve işleme başlar.

Bir sonraki yazımda ise critical section, race condition, locking, synchronized ve reentrantlock yapılarından bahsediyor olacağım. Takipte kalınız :)

--

--

Ibrahim Halil Koyuncu
Ibrahim Halil Koyuncu

No responses yet