Birden fazla işlemin tek bir program akışı içinde gerçekleştirilmesi
Java'da Thread sınıfları sayesinde gerçekleştirilmektedir. Şu ana kadar
yaptığımız bütün örnekler ana program akışını takip ederek
gerçekleştirilen işlemleri içermekteydi. Thread (iş parçacığı)
kullanımı, birden fazla işlemin tek bir akışı paylaşarak neredeyse
eşzamanlı bir şekilde gerçekleşmesini sağlar.
Thread kullanımına en iyi örnek oyun uygulamalarıdır. Kelime oyunu
uygulamasında kullanıcının ana ekranda oyunu oynadığını düşünelim. Biz
ise tam oyunun en heyecanlı yerinde rasgele bir reklam gösterip
kullanıcının bütün dikkatini reklama yöneltmek isteyelim :). Bu amaçla
bizim reklamı uzaktaki bir sunucudan çekip ana ekrana basan bir kod
parçası yazmamız gerekmektedir. Uzaktan dosya yükleme uzun bir işlem
olduğu için işlem sırasında oyunu bloke etmememiz gerekir. Bu yüzden
Thread mantığını kullanan bir kod yazarak ana akışta hem oyunu hem de
yükleme işlemini ilerleterek iki işlemin de mevcut kaynakları paylaşarak
kullanmasını sağlayabiliriz. Bu sayede hem kullanıcı oyununu kesintisiz
oynamaya devam eder hem de reklam dosyaları uygulamaya yüklenmiş olur.
Java'da Thread kullanan program örneği ise aşağıda verilmiştir;
public class MyThread implements Runnable {
private int end;
private String name;
public MyThread(String name, int end) {
this.end = end;
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < end; i++) {
System.out.println(name + " : " + i);
}
}
}
Thread içerisinde gerçekleştirilecek işlemler öncelikle Runnable
Interface'ten üretilmiş herhangi bir sınıfta tanımlanmalıdır. Yukarıda
yer alan örnek kodda MyThread adında tanımlanmış bir iş parçacığı
bulunmaktadır. Runnable içerisindeki run metodu içinde
ise yapılması gereken işlemler belirtilir. Örnek kod yapıcı
(constructor) metot içinde verilen bir int değeri kadar sayma işlemi
yapacak ve bunu konsola Thread'e verilen isimle birlikte yazacaktır.
public class ThreadLesson {
public static void main(String[] args) {
Thread thread1 = new Thread(new MyThread("thread1", 6));
Thread thread2 = new Thread(new MyThread("thread2", 5), "thread2");
thread1.start();
thread2.start();
}
}
MyThread adlı iş parçacığını çalıştırmak için Thread
adlı sınıftan faydalanırız. Yukarıdaki örnekte farklı Thread
tanımlamaları ve bunların kullanımları görülmektedir. Thread'ler
tanımlanırken yapıcı içerisinde Thread'e ait bir isim de verilebilir.
Yukarıdaki kodun çıktısı aşağıdaki gibidir;
thread1 : 0
thread2 : 0
thread2 : 1
thread2 : 2
thread1 : 1
thread2 : 3
thread1 : 2
thread2 : 4
thread1 : 3
thread1 : 4
thread1 : 5
Eğitimin alt başlıklarında threadler ile alakalı setPriority
methodu ve Executor Sınıfı hakkında anlatımlar ve örnekler yapacağız.
Öncelik Belirleme
Bir üst ders içeriğinde anlatmış olduğumuz theradlerin çalışma önceliklerini belirlemek için setPriority metodunu kullanılmaktadır. Aşağıdaki örnekte thread3 en yüksek öneme sahipken diğeri daha az önemli olarak tanımlanmıştır.
public class ThreadLesson {
public static void main(String[] args) {
Thread thread1 = new Thread(new MyThread("thread1", 6));
Thread thread2 = new Thread(new MyThread("thread2", 5));
Thread thread3 = new Thread(new MyThread("thread3", 4));
thread3.setPriority(Thread.MAX_PRIORITY);
thread1.setPriority(Thread.MIN_PRIORITY);
thread2.setPriority(Thread.MIN_PRIORITY);
thread1.start();
thread2.start();
thread3.start();
}
}
Bu örnegimizde setPriority(Thread.MAX_PRIORITY) koduyla
birlikte threadimizin en öncelikli çalışmasını
sağlarken, setPriority(Thread.MIN_PRIORITY) koduyla threadimizin en
düşük öncelikle çalışmasını sağladık.
İşlemin çıktısına baktığımızda ise thread1.start() kod da daha önce
olmasına rağmen thread3 önceliklendirildiği için işlemini ilk bitirir.
thread1 : 0
thread2 : 0
thread3 : 0
thread1 : 1
thread3 : 1
thread2 : 1
thread3 : 2
thread1 : 2
thread3 : 3
thread2 : 2
thread1 : 3
thread2 : 3
thread1 : 4
thread2 : 4
thread1 : 5
Bu şekilde dilediğimiz kadar iş parçası tanımlayıp eşzamanlı olarak
istediğimiz kadar işlem yapabilmekteyiz. Ancak unutulmaması gereken,
oluşturulan her Thread'in sistemin belleğinden ve işlemciden bir pay
aldığıdır. Aşırı sayıda Thread oluşturulması mikroişlemcinin işlemler
arası geçiş yapması gerektiğinden ciddi performans kayıplarına yol
açacaktır. Bu işlemleri düzenlemek için bir sonraki eğitim içeriğinde Executor sınıfının özelliklerini kullanacağız.
Threadların Kontrolü ve Düzenlenmesi
Thread sayısının ve çalışmasının düzenli ve kontrollü bir şekilde
gerçekleştirilmesi için Java bize Executor adında bir sınıf sunmaktadır.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ThreadLesson2 {
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 20; i++) {
Thread thread = new Thread(new MyThread("thread" + i, 3));
executor.execute(thread);
}
executor.shutdown();
executor.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("Done");
}
}
ExecutorService bize belli bir anda en fazla kaç Thread çalıştırmak istediğimizi sormaktadır. Yukarıdaki örnek newFixedThreadPool metodu ile 5 farklı iş parçasının aynı anda çalıştırılabileceği belirtilmiştir. Daha sonrasında for döngüsü içinde 20 adet Thread tanımlanmasına rağmen executor
servisi gelen işleri düzene sokar ve 5 Thread üzerinde işlem
gerçekleştirmez. Sonradan eklenen işlemler sıraya (queue) sokulur ve
mevcut işlemler bitirildikçe çalıştırılır. Böylece sistem kaynakları
işlem parçaları tarafından kontrolsüzce harcanamaz. shutdown metodu ise yeni işlem alımını durdurur ve mevcut işlemlerin bitirilmesini sağlar. awaitTermination ise mevcut işlemlerin bitirilmesi için belirli bir süre tanır ve bu sürenin sonunda ExecutorService tamamen kapatılır.
Tanımlanan işlemlerin belirli bir zaman sonra otomatik başlatılması
ya da sistematik olarak belli bir frekansla çalıştırılması için Java
bizlere ScheduledExecutorService adında bir sınıf
sunar. Bu sistem sayesinde yapılan işlemlerin belirli bir tarihte
başlamasını ya da sürekli olarak işlemin tekrar edilmesini
sağlayabiliriz.
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ThreadLesson3 {
public static void main(String[] args) throws InterruptedException {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
MyThread worker = new MyThread("Thread 1", 3);
pool.schedule(worker, 5, TimeUnit.SECONDS);
Thread.sleep(20000);
pool.shutdown();
}
}
Yukarıdaki örnekte ExecutorService yerine ScheduledExecutorService
kullanılarak işlemin ileri bir tarihte gerçekleşmesi sağlanır. schedule
metodu ile verilen iş parçası 5 saniye gecikmeli çalışacaktır.
Thread.sleep metodu ise ana akışı durdurur ancak MyThread ile tanımlanan
worker iş parçası ScheduledExecutorService tarafından çalıştırılmaktadır. Konsol çıktısı aşağıdaki gibidir;
Thread 1 : 0
Thread 1 : 1
Thread 1 : 2
Dilerseniz scheduleAtFixedRate metodu ile işlemin sürekli çalışmasını sağlayabilirsiniz.
Kaynaklar:
- https://gelecegiyazanlar.turkcell.com.tr/konu/android/egitim/android-101/threadlerin-kontrolu-ve-duzenlenmesi
- https://gelecegiyazanlar.turkcell.com.tr/konu/android/egitim/android-101/oncelik-belirleme-setpriority-metodu
- https://gelecegiyazanlar.turkcell.com.tr/konu/android/egitim/android-101/threadler