Java

Java Multithreading Kullanımı

Herhangi bir uygulamada birden fazla işlem olabilir (örnekler). Bu işlemin her biri, tek bir iş parçacığı veya birden çok iş parçacığı olarak atanabilir.

Bu yazıda aynı anda birden fazla görevi nasıl gerçekleştireceğimizi ve aynı zamanda bu görevler arasındaki senkronizasyon hakkında daha fazla bilgi öğreneceğiz.

Single Thread Nedir?

Single thread (tek iş parçacığı), bir bilgisayar programının veya işleminin aynı anda yalnızca bir işlemi gerçekleştirebildiği bir işleme modelini ifade eder. Bu model, bir programın sıralı olarak adım adım çalıştığı ve aynı anda birden fazla görevi yerine getiremediği anlamına gelir.

Programlamada, iki farklı tip thread kullanılır: User Thread (Kullanıcı İş Parçacığı) ve Daemon Thread (Arka Plan İş Parçacığı). Bu iki tür iş parçacığı, uygulamanın farklı ihtiyaçlarına yönelik olarak tasarlanmıştır.

User Thread

User Thread (Kullanıcı İş Parçacığı): Bu tip iş parçacığı, kullanıcı tarafından başlatılan ve kontrol edilen bir işlemler grubunu temsil eder. Kullanıcı iş parçacıkları, uygulama kodu tarafından oluşturulur ve kullanıcı tarafından yönetilir. Bu iş parçacıkları, genellikle kullanıcının talepleri doğrultusunda belirli görevleri gerçekleştirmek üzere kullanılır.

Daemon Thread

Daemon Thread (Arka Plan İş Parçacığı): Bu tür iş parçacığı, genellikle uygulamanın temizlenmesi (shutdown) sürecinde arka planda çalışan, kullanıcı tarafından doğrudan kontrol edilmeyen işlemleri yönetir. Örneğin, bir uygulamanın sonlanması sırasında kaynakları serbest bırakmak veya belirli işlemleri tamamlamak için kullanılabilirler. Daemon thread’leri genellikle programın ana işlevselliğini etkilemeden arka planda çalışan görevleri yönetmek için kullanılır.

Kullanım Örnekleri

Yukarıdaki Java kodu, kullanıcı iş parçacığı (userThread) ve daemon iş parçacığı (daemonThread) örneklerini göstermektedir. Thread.sleep() metodu kullanılarak her iki iş parçacığı da belli bir süre bekletilmektedir. daemonThread.setDaemon(true) ifadesi ile daemon iş parçacığı olarak işaretlenmiştir. Bu sayede, kullanıcı iş parçacığı tamamlanmadan önce daemon iş parçacığı programın sonlanmasını beklemeden kendi işlemini tamamlayabilir.

Java’da Thread sınıfının kullanımı oldukça yaygındır ve bu örnek, konseptin anlaşılması için temel bir yapı sağlamaktadır.

Single Thread modelinin avantajları şunlardır:

  1. Basitlik ve Anlaşılırlık: Single Thread modeli, sıralı bir şekilde işlemlerin gerçekleştirilmesine dayandığından, kodun basit ve anlaşılır olmasını sağlar. Her adım sırasıyla gerçekleşir, bu da kodun takip edilmesini kolaylaştırır.
  2. Düzenlilik ve Tahmin Edilebilirlik: Tek iş parçacığı kullanmak, işlemlerin düzenli bir şekilde tamamlanmasını sağlar. Bu düzenlilik, programın beklenen bir şekilde davranmasını ve hataların daha kolay tespit edilmesini sağlar.
  3. Synchronization Sorunları Yoktur: Birden fazla iş parçacığının eşzamanlı olarak çalıştığı çoklu iş parçacığı modellerindeki gibi senkronizasyon sorunlarından kaçınılır. Tek bir iş parçacığı olduğu için paylaşılan kaynaklara erişimle ilgili sorunlar daha az olabilir.
  4. Platform Bağımsızlık: Single Thread modeli, genellikle platform bağımsızdır. Programın çalışma şekli genellikle işletim sisteminden çok programın kendisine bağlıdır, bu da platform değişikliklerinin etkilerini azaltabilir.
  5. Hafif ve Düşük Kaynak Kullanımı: Tek iş parçacığı kullanan programlar, çoklu iş parçacığı kullananlara göre daha az bellek ve işlemci gücü tüketebilir. Bu durum, özellikle basit ve küçük ölçekli uygulamalarda avantajlı olabilir.
  6. Debugging Kolaylığı: Single Thread modeli, hata ayıklama sürecini basitleştirebilir. Çünkü her şey sıralı bir şekilde gerçekleştiği için, hataların kaynağını bulmak ve düzeltmek daha kolay olabilir.

Ancak, bu avantajların yanında Single Thread modelinin sınırlamalarını ve özellikle performans açısından bazı durumlarda yetersiz kalabileceğini unutmamak önemlidir. Çoklu iş parçacığı kullanmanın avantajlarına ve uygulamanın ihtiyaçlarına bağlı olarak, bu iki model arasında bir tercih yapmak gerekebilir.

Multithreading Nedir?

Java’da multithreading, aynı anda iki veya daha fazla iş parçacığının yürütme işlemidir. Bu, bilgisayarın CPU kaynaklarını daha etkili bir şekilde kullanarak programların daha hızlı ve verimli çalışmasını sağlar. Multithreading, özellikle büyük ve karmaşık uygulamalarda performans artışı elde etmek için yaygın olarak kullanılır.

Çalışma Prensibi

Her bir iş parçacığı (thread), kendi işlem akışına sahiptir ve bağımsız olarak çalışabilir. İş parçacıkları, aynı program içinde paylaşılan kaynaklara (örneğin, bellek) erişebilir, ancak bu durum senkronize edilmelidir. Multithreading, eşzamanlılık (concurrency) ilkesine dayanır; yani, birden fazla iş parçacığı aynı anda çalışabilir, ancak her biri kendi hızında ilerler.

Kullanım Örnekleri

Multithreading, çeşitli senaryolarda kullanılabilecek güçlü bir araçtır. İşte multithreading’in kullanım örnekleri ve detaylı açıklamaları:

1. İleri ve Geri Sayım Uygulaması:

Bu örnekte, aynı anda iki iş parçacığı, biri ileri sayım yaparken diğeri geri sayım yapmaktadır.

2. Dosya İşleme Uygulaması:

Bu örnekte, dosya okuma ve yazma işlemleri aynı anda iki iş parçacığı tarafından gerçekleştirilmektedir.

3. Web Scraping Uygulaması:

Bu örnekte, web scraping işlemi için bir iş parçacığı kullanılmıştır. İnternet üzerinde veri çekme işlemleri aynı anda diğer işlemlerle paralel olarak gerçekleştirilebilir.

Bu örnekler, multithreading’in farklı senaryolarda nasıl kullanılabileceğini göstermektedir. Ancak, multithreading kullanırken senkronizasyon ve paylaşılan kaynaklara dikkat etmek önemlidir.

Avantajları

  1. Performans Artışı: Çoklu iş parçacığı kullanarak, programlar aynı anda birden fazla görevi gerçekleştirebilir, bu da toplam işlem hızını artırabilir.
  2. Daha Hızlı Cevap Süreleri: İş parçacıkları, bir işlem tamamlanmadan diğerine geçebilir, bu da kullanıcıya daha hızlı yanıt süreleri sunabilir.
  3. Paralel Programlama: Multithreading, görevleri paralel olarak işleyerek paralel programlamayı destekler.
  4. Daha Etkin Kaynak Kullanımı: İş parçacıkları, birbirinden bağımsız olarak çalıştıkları için kaynakların daha etkin bir şekilde kullanılmasını sağlar.

Dikkat Edilmesi Gerekenler

  1. Senkronizasyon Sorunları: Aynı anda çalışan iş parçacıkları arasında senkronizasyon sorunlarına dikkat edilmelidir. Paylaşılan kaynaklara aynı anda erişim, uyumsuzluklara yol açabilir.
  2. Deadlock ve Yarış Koşulu: Yanlış kullanıldığında, multithreading, deadlock (kilitlenme) ve yarış koşulu gibi sorunlara neden olabilir. Bu tür durumlar, dikkatli bir tasarım ve kodlama gerektirir.
  3. İşlemci Yoğunluğu: Çok sayıda iş parçacığı olması, işlemcinin yoğun olarak kullanılmasına ve sistem kaynaklarının tükenmesine neden olabilir.

Java’da iş parçacığı yaşam döngüsü

Java’da bir iş parçacığının yaşam döngüsü şu aşamalardan oluşur:

AşamaMetot / OlayAçıklama
Yaratma (Creation)Thread Sınıfı İnstancesiBir iş parçacığı oluşturularak Thread sınıfının bir örneği yaratılır.
Başlatma (Start)start() Metodustart metodu, iş parçacığı için yeni bir işlem başlatır ve run metodu çağrılır.
Çalışma (Runnable)run() Metoduİş parçacığının ana çalışma mantığı bu metot içerisine yazılır.
Bekleme (Sleep, Wait)sleep() veya wait() Metoduİş parçacığı, belirli bir süre boyunca bekletilebilir. sleep veya wait metotları kullanılır.
Bekleme Sonrası UyanmaBelirli bir süre boyunca bekleyen iş parçacığı, beklemenin sona ermesiyle uyanır ve çalışmaya devam eder.
Bekleme Sonrası İnteruptinterrupt() MetoduBekleme sırasında bir iş parçacığına müdahale etmek ve onu uyandırmak için interrupt metodu kullanılabilir.
Duraklatma (Suspend)suspend() Metoduİş parçacığı duraklatılır. Bu metot genellikle kullanılmaz, çünkü senkronizasyon sorunlarına neden olabilir.
Devam Ettirme (Resume)resume() MetoduDuraklatılmış bir iş parçacığına devam etmesi için resume metodu kullanılabilir. Genellikle kullanılmaz.
Durdurma (Stop)stop() Metoduİş parçacığı tamamen durdurulur. Bu metot genellikle kullanılmaz, çünkü güvenlik sorunlarına ve hatalara neden olabilir.
Bitiş (Termination)run Metodunun Tamamlanmasıİş parçacığı, run metodunun tamamlanması veya stop metodu çağrıldığında sonlanır.
Java’da iş parçacığı yaşam döngüsü

1. Yaratma (Creation):

Yeni bir iş parçacığı oluşturulur. Thread sınıfının bir örneği myThread yaratılır. Bu aşamada iş parçacığı yaratılmış, ancak henüz başlatılmamış durumdadır.

2. Başlatma (Start):

start metodu çağrılarak iş parçacığı başlatılır. Bu metodun çağrılması, iş parçacığının çalışma sürecini başlatır ve run metodu otomatik olarak çağrılır.

3. Çalışma (Runnable):

run metodu içinde iş parçacığının ana çalışma mantığı yazılır. Bu kısımda işlemler gerçekleştirilir.

4. Bekleme (Sleep, Wait):

İş parçacığı, belirli bir süre boyunca bekletilebilir. Bu örnekte, sleep metodu kullanılarak iş parçacığı 1 saniye boyunca bekletilmektedir.

5. Bekleme Sonrası İnterrupt:

Bekleme sırasında bir iş parçacığına müdahale etmek ve onu uyandırmak için interrupt metodu kullanılabilir.

6. Duraklatma (Suspend):

İş parçacığı duraklatılır. Ancak, suspend metodu güvenlik ve senkronizasyon sorunlarına neden olduğu için genellikle kullanılmaz.

7. Devam Ettirme (Resume):

Duraklatılmış bir iş parçacığına devam etmesi için resume metodu kullanılabilir. Ancak, bu metodun kullanımı güvenlik sorunlarına neden olabilir.

8. Durdurma (Stop):

İş parçacığı tamamen durdurulur. Ancak, stop metodu güvenlik sorunlarına ve hatalara neden olduğu için genellikle kullanılmaz.

9. Bitiş (Termination):

run metodu içindeki kodlar tamamlandıktan sonra iş parçacığı sonlandırılır. Bu durum, iş parçacığının çalıştığı görevin tamamlandığı durumu temsil eder.

java Thread Metotları

Threadler için yaygın olarak kullanılan yöntemlerden bazıları şunlardır:

MethodTanımıÖrnek
start()İş parçacığının yürütülmesini başlatır ve JVM iş parçacığı üzerinde run() yöntemini çağırır.java Thread myThread = new MyThread(); myThread.start();
sleep(int millisecond)İş parçacığının uyku moduna geçmesini sağlar, böylece iş parçacığı belirtilen milisaniye için duraklar ve ardından çalışmaya devam eder. Bu konuların senkronizasyonunda yardımcı olur.java try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
getName()Thread ismini döndürür.java String threadName = myThread.getName();
setPriority(int newPriority)Thread önceliğini değiştirir.java myThread.setPriority(Thread.MAX_PRIORITY);
yield()Durmadaki mevcut thread ve diğer threadin yürütülmesine neden olur.java Thread.yield();

Tablo üzerindeki metotlara örneklerle bakalım:

Bu örnekler, Java’da yaygın olarak kullanılan thread yöntemlerini göstermektedir. Ancak, setPriority ve yield gibi yöntemleri kullanırken dikkatli olunmalıdır, çünkü bazı durumlarda istenmeyen davranışlara neden olabilirler.

1. start()

Bu örnekte, start metodu çağrılarak iş parçacığı başlatılır ve run metodu otomatik olarak yürütülür.

2. sleep(int millisecond)

Bu örnekte, sleep metodu kullanılarak iş parçacığı 2 saniye boyunca bekletilir.

3. getName()

Bu örnekte, getName metodu kullanılarak iş parçacığının adı alınır.

4. setPriority(int newPriority)

Bu örnekte, setPriority metodu kullanılarak iş parçacığının önceliği en yükseğe (MAX_PRIORITY) ayarlanır.

5. yield()

Bu örnekte, yield metodu kullanılarak iş parçacığının kontrolü diğer threadlere geçirilir.

Bu örnekler, ilgili thread metotlarının nasıl kullanılacağını göstermektedir.

Java Thread Senkronizasyonu

Multithreading ve Senkronizasyon

Multithreading’de, programların zaman uyumsuz davranışı olabilir, özellikle de birden çok iş parçacığı aynı kaynaklara erişiyorsa. Bu durum, uygulamada tutarsızlıklara ve hatalara neden olabilir. Paylaşılan kaynaklara güvenli bir şekilde erişmek ve tutarlılık sağlamak için senkronizasyon kullanılır.

Java’da, senkronize davranış sağlamak için senkronize metotlar kullanılır. Senkronize bloklar, bir iş parçacığının belirli bir nesne üzerinde senkronize edilmiş bir bloğa ulaştığında, başka hiçbir iş parçacığının o bloğu aynı anda çağıramayacağı şekilde çalışır. Bu, bir iş parçacığının bir senkronize bloğu bitirmesini bekleyen diğer tüm iş parçacıklarına izin verir.

Örnek olarak:

Bu örnekte, synchronized anahtar kelimesi kullanılarak lockObject üzerinde senkronize bir blok oluşturulmuştur. Bu blok içindeki ifadeleri bir iş parçacığı yürütürken diğer iş parçacıkları beklemek zorundadır.

Bu senkronizasyon yöntemi, çoklu iş parçacıklı uygulamalarda veri tutarlılığını sağlamak için kullanılır ve paylaşılan kaynaklara güvenli erişimi temin eder.

Daha basit bir örnek ile devam edelim:

Açıklamalar:

  1. sharedCounter: Bu değişken, iki iş parçacığı tarafından paylaşılacak ve senkronize edilecek olan ortak bir sayacı temsil eder.
  2. Runnable incrementTask: Bu, Runnable arayüzünü kullanarak bir artırma görevini tanımlayan bir lambda ifadesidir. İki iş parçacığı aynı incrementTask‘ı kullanacak ve sharedCounter‘ı artırmaya çalışacak.
  3. synchronized (SynchronizationSimpleExample.class): synchronized anahtar kelimesi, parantez içine yazılan nesne üzerinde senkronizasyonu sağlar. Burada, sınıfın kendisi üzerinde senkronizasyon sağlıyoruz.
  4. for (int i = 0; i < 1000; i++): İki iş parçacığı, her biri 1000 defa dönen bir döngü içinde sharedCounter‘ı artırmaya çalışacak.
  5. Thread thread1 = new Thread(incrementTask);: İki farklı iş parçacığı oluşturuyoruz, ancak her ikisi de aynı incrementTask‘ı kullanacak.
  6. thread1.start(); ve thread2.start();: İki iş parçacığını başlatıyoruz. İş parçacıkları aynı anda çalışacak ve sharedCounter‘ı artırmaya çalışacaklar.
  7. thread1.join(); ve thread2.join();: Ana iş parçacığı, diğer iki iş parçacığının tamamlanmasını bekler. Bu, iş parçacıklarının tamamlanmasını beklerken diğer işlemlerin devam etmesini engeller.
  8. System.out.println("Paylaşılan Counter: " + sharedCounter);: Sonuçları ekrana yazdırma. Senkronizasyon kullanıldığı için, iki iş parçacığı da sharedCounter‘ı güvenli bir şekilde artırmış olacaktır.

Örnek 2:

Bu örnekte, çalıştırılabilir bir arabirimin run() ve start() yöntemini geçersiz kılma yöntemlerini ve bu sınıfın iki iş parçacığını oluşturup bunları uygun şekilde çalıştıracağız.

Başka bir örnek daha:

Eksik olan senkronizasyonu eklemek için synchronized anahtar kelimesini kullanabiliriz. Aşağıda, run metodu içindeki işlemleri senkronize etmiş bir şekilde güncellenmiş bir örnek bulunmaktadır:

Bu örnekte, run metodu içindeki işlemleri synchronized (lock) bloğu içine aldık. Bu sayede TasarimThread‘lere ait iki farklı iş parçacığı aynı anda bu bloğa giremez, bu da senkronizasyonu sağlar.

Multithreading ve Senkronizasyon Özeti

Java’da multithreading, birçok iş parçacığının aynı anda çalıştığı ve genellikle paylaşılan kaynaklara erişimde senkronizasyon sorunlarının ortaya çıkabileceği bir konsepttir. Senkronizasyon, bu tür sorunları önlemek ve tutarlılık sağlamak için kullanılır.

Senkronizasyon Yöntemleri:

  1. Synchronized Bloklar:
    • synchronized anahtar kelimesi kullanılarak belirli bir nesne üzerinde senkronize bloklar oluşturulur.
    • Bu bloklar, aynı anda sadece bir iş parçacığı tarafından çalıştırılabilir, bu da veri tutarlılığını sağlar.

Lock ve ReentrantLock:

  • Lock arayüzü ve ReentrantLock sınıfı, synchronized anahtar kelimesinden daha gelişmiş kontrol sağlar.
  • lock() ve unlock() metotları ile senkronizasyon sağlanır.

Örnek Uygulama:

Bu örnek, senkronizasyonu kullanarak iki iş parçacığının aynı anda paylaşılan bir değişkene güvenli bir şekilde erişimini kontrol etmektedir. Senkronizasyon, çoklu iş parçacıklı uygulamalarda veri tutarlılığını sağlamak için önemli bir araçtır.

Yorum Yap

Yorum yapmak için tıklayın