
Bugünkü makalemizin konusu java’da eş zamanlı programlama. Peki nedir eş zamanlı programlama? Sizinde tahmin ettiğiniz gibi işlemci tarafından aynı anda iki veya daha fazla işlemin yürütülmesi anlamına geliyor. Peki bir işlemci aynı anda iki işlemi yürütebilir mi? Mesela insan vücudunu düşünürsek aynı anda birden fazla işlem yapabiliyoruz. Ancak bir tane işlemcimiz var. O da beynimiz. Peki biz aynı anda birden fazla işlemi nasıl yapıyoruz? Multithread çalışarak. Yani beynimize birden fazla işlem yaparak bu işlemi gerçekleştiriyoruz. Günümüz işlemcilerinde çekirdek sayısı en az 2 konumunda. Bu yüzden aynı anda iki işlem yaptırmak mantıklı gibi gözüküyor. Peki tek çekirdekli bir işlemci kullanıyor olsaydık durum ne olurdu. O zaman da işletim sistemi bizim için işleri bir sıraya koyarak sanki aynı anda birden fazla işlem yapıyormuş izlenimi verirdi. Yani yapması gereken işleri çok küçük zaman aralıklarına bölerek o işlemleri gerçekleştirirdi. Buna işletim sisteminin quanta zamanı deniyor. Windows işletim sisteminde quanta zamanı 20 ms. Yani işletim sistemi çalıştırdığı her process’ i 20 ms de bir değiştiriyor. Tabi bu zaman aralığı değiştirilebilir. Bazı işlemler hayati önlem taşıyorsa onların önceliği arttırılabilir yada azaltabiliriz. Ayrıca bazı işletim sistemleri var ki bütün bu scheduling işlemlerini kullanıcıya bırakıp gerçek zamanlı işlemler yaptırabiliyor. Bu konulara daha sonra değineceğiz. Konumuza dönecek olursak;
Processes
Java’ da yazdığımız her programa bir process deriz. İşlemci tarafından her program bir process olarak adlandırılır. En azından her programda bir adet process bulunmaktadır. Her process’ in kendi bellek bölgesi ve çalışma zamanı kaynakları bulunmaktadır. İşletim sistemi process’ler arası mesajlaşmayı kendisi ayarlamaktadır. Ve bu iletişime IPC(Inter Process Comminication) denmektedir.
Threads
Process’ler ise thread’lerden oluşmaktadır. Yani işe parçacıklarından. Her process en azından bir adet thread bulundurmaktadır. Bizde bu thread’ a main thread demekteyiz. Main thread javada herhangi bir public sınıfta bulunan public static void main(String[] args) metodu ile başlar. Ve daha sonra yönetim bu thread üzerine geçerek başka thread’lerde yaratılabilir. Bu tür çoklu iş parçacıkları kullanmak işlemci performansını da arttırmaktadır. Java 5.0 ile birlikte java.util.Concurency paketi altında High Level bir çok concureny API’ si gelmiştir. Sırası geldikçe api’ leri inceleyeceğiz. Thread’ler genelde lightweight process ler olarak adlandırılmaktadır. Process yaratılmasından daha kolay ve daha az maliyetlidir. Yaratıldan thread ler çalıştığı process in hafızasını kullanmaktadırlar. Bu yüzden hafıza problemlerini önelemek için threadlerin yönetimi programcı tarafından yapılamktadır. Bunun içinde bir çok yol mevcuttur. Sırasıyla bu metodları inceleyeceğiz.
Thread Nesneleri
Java’ da herşeyin sınıflar ile gerçekleştirildiğini biliyoruz. Bu sebeple thread işlemleride sınıflar kullanılarak yapılmaktadır. Thread ile ilgili sınıflar java.util.Concurency paketi altında bulunmaktadır. Thread oluşturma işlemi 2 şekilde gerçekleştirilebiliriz.
1. Thread’ ler üzerinden tek tek kontrol yapabileceğimiz Thread sınıfını kullanarak.
2. Thread yönetimini Executor nesnesi ile yaparak.
Biz yazımız boyunca ilk metodu kullacağız. Yani threadleri kendimiz oluşturup yönetmeye çalışacağız.
Thread Oluşturma
Thread oluşturma işlemini 2 şekilde gerçekleştirebiliriz.
1. runnable interface’ ini kullanarak. Bu interface bizi run adında bir metod implement etmeye zorlayacaktır.
package net.bsayiner.ThreadSample;
public class HelloRunnable implements Runnable{
@Override
public void run() {
System.out.println("Hello from a thread");
} // End of Implemented Method run()
} // End of Class HelloRunnable
HelloRunnable.class
Daha sonra main metodu yani main thread içerisinde bir adet Thread nesnesi oluşturarak, yapıcısında runnable interface’inden türeyen HelloRunnable nesnesini veriyoruz.
package net.bsayiner.ThreadSample;
public class Driver {
public static void main(String[] args) {
Thread thread = new Thread(new HelloRunnable());
thread.start();
} // end of Method main
} // End of Class Driver
Driver.class
Dikkat ederseniz burada thread nesnesi oluşturulduktan sonra thread.start() metodu ile thread başlatılıyor.
2. Bu yöntem ise Thread sınıfını extend ederek run metodu override etmek olacak.
package net.bsayiner.ThreadSample;
public class HelloThread extends Thread{
@Override
public void run(){
System.out.println("Hello from a thread");
} // End of Extended Method run()
} // End of Class HelloThread
HelloThread.class
package net.bsayiner.ThreadSample;
public class Driver {
public static void main(String[] args) {
HelloThread helloThread = new HelloThread();
helloThread.start();
} // end of Method main
} // End of Class Driver
Driver.class
Bu şekilde de Thread sınıfını extends ettiğimiz için thread sınıfının run metodunu dışarıdan direk nesne üzerinden çağırabiliyoruz. Peki bu iki yöntemden hangisi tercih edilmeli. Genelde runnable interface’ ini implement etmek daha mantıklı gözüküyor. Çünkü java çoklu kalıtıma izin vermiyor ancak çoklu implementasyonlar gerçekleştirilebiliyor. Burada unutulmaması gereken nokta thread nesneleri yaratıldıktan sonra, thread sınıfının start() metodu çağırılarak thread’ler başlatılmıştır.
Thread’leri Uyutmak
Çalıştırdığımız programlarda bazı thread’leri uyutmak isteyebiliriz. Bunun için kullanacağımız metod Thread sınıfının static bir metodu olan Thread.sleep() olacaktır. sleep metodunun overloaded 2 çeşidi bulunmaktadır. Bunlardan birincisi içerisine milisaniye cinsinden deger alır. Diğeri ise ms değerine ek olarak nanosaniye cinsinden de değer alır. Ancak bu iki zamanında kesinliği JVM tarafından kesin olarak garanti edilemez. Çünkü bu bekleme süreleri işletim sistemi tarafından hesaplanmaktadır.
package net.bsayiner.ThreadSleeping;
public class SleeperClass implements Runnable {
private String[] message = new String[] { "First message",
"Second message", "Third message", "Fourth message" };
@Override
public void run() {
System.out.println("Messages is writing");
for (int i = 0; i < message.length; i++) {
System.out.println(message[i]);
try {
Thread.sleep(2000); // Thread 2 sn uyutuluyor
} catch (InterruptedException e) {
e.printStackTrace();
} // End of Try
} // End of For Loop
} // End of Implemented Method run()
} // End of Class SleeperClass
SleeperClass.class
Yukarıdaki kodlara dikkat ederseniz Thread.sleep() metodu bir InterruptedException hatası fırlatıyor. Sebebi ise thread uyuyor iken dışarıdan bir interrupt isteği gelebilir. Ve bu anda InterruptedException istisnası fırlatılabilir.
package net.bsayiner.ThreadSleeping;
public class Driver {
public static void main(String[] args) {
Thread sleeperThread = new Thread(new SleeperClass());
sleeperThread.start();
} // End of Method main
} // End of Class Driver
Driver.class
main metodumuzun içerisinde SleeperThread sınıfında bir instance alınarak Thread nesnesine parametre olarak verilmekte ve thread nesnesinin start metodu ile birlikte ayrı bir thread açılarak işlemler yaptırılmaktadır.
Interrupts
Interrupt kelimesinin türkçe karşılığı Kesme İsteği‘ dir. Microişlemcilerde sıkça kullanılmaktadır. Bilgisayar programcılığında ise interrupt çalışan bir thread’ in yaptığı işi derhal bırakıp interrupt istediği geldiğinde yapılacak işlemleri yapmasıdır. Genelde bu interrupt istedği ile thread sonlandırılır. Ancak bazı durumularda başka işlemler yapılması isteniyor olarabilir. Interrupt uygulamalarının mantıklı olarak çalışabilmesi için kesme istedği uygulanan thread içerisinde, kesme uygulandığında yapılacak işlemler belirtilmelidir. Peki thread nesnleleri kendi interrupt olaylarını nasıl tanımlayacaklar. Eğer thread içerisinde bir döngü varsa ve işlemler bu döngü içerisinde yapılıyorsa, InterruptedException burada fırlatılabilir. İsterseniz bir örnek ile konuyu pekiştirelim.
package net.bsayiner.InterruptRequest;
public class InterruptedThread implements Runnable {
private String[] works = new String[] { "First work", "Second work",
"Third work", "Fourth work" };
@Override
public void run() {
for (int i = 0; i < works.length; i++) {
System.out.println(works[i]);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println("Method terminated");
return; // Interrupt isteği gelirse metod' dan çıkılıyor.
}
} // End of For Loop
} // End of Implemented Method run()
} // End of Class InterruptedThread
InterruptedThread.class
Gördüğünüz gibi runnable metodunun çalıştığı thread dışasıran interrupt() metodu ile tetiklenirse InterruptedException istisnası oluşturuluyor catch istisna yakalanıyor ve return ile metoddan çıkılıyor. main metodumuzun bulunduğu sınıf ise şöyle;
package net.bsayiner.InterruptRequest;
public class Driver {
public static void main(String[] args) {
Thread thread = new Thread(new InterruptedThread());
thread.start();
try {
Thread.sleep(2200);
} catch (InterruptedException e) {
e.printStackTrace();
} // End of Try
thread.interrupt();
} // End of Method main()
} // End of Class Driver
Driver.class
Yukarıda verdiğimiz örneklerde Thread.sleep ile bir InterruptedException yakalamış ve buna gore programımızı yönlendirmiştik. Peki programımızda bir sleep kullanmayacaksak ve run metodumuzun çalıştığı thread’ in bir interrupt alıp almadığını nasıl anlayacağız. Bunu için yine bir döngü içerisinde Thread sınıfının static bir metodu olan Thread.interrupt() metodunu kullanabiliriz. Hemen kodlara göz atalım;
package net.bsayiner.ThreadInterrupted;
public class ThreadInterrupted implements Runnable {
private String[] works = new String[] { "First work", "Second work",
"Third work", "Fourth work" };
@Override
public void run() {
System.out.println("Thread started");
for (int i = 0; i < works.length; i++) {
System.out.println(works[i]);
if (Thread.interrupted()) { // Thread interrupt edilmişmi kontrol ediliyor.
System.out.println("Thread terminated");
return;
} // End of If Case
} // End of For Loop
} // End of Implemented Method run()
} // End of Class ThreadInterrupted
ThreadInterrupted.class
package net.bsayiner.ThreadInterrupted;
public class Driver {
public static void main(String[] args) {
Thread thread = new Thread(new ThreadInterrupted());
thread.start();
thread.interrupt(); // thread interrupt ediliyor
} // End of Method main()
} // End of Class Driver
Driver.class
Yukarıdaki örnekte run metodunun içerisinde Thread.sleep gibi bir checked exception fırlatan metod kullanılmadığı için, for döngüsü içerisinde if ile Thread.interrupted() metodu kullanılarak thread durumu kontrol ediliyor. Ancak burada çok önemli bir durum söz konusu. Thread.interrupted() metodu çağrıldığında interrupt flag temizlenir. Çünkü bir interrupt oluştuğunda interrupt flag’ ı true olur. Biz Thread.interrupt() metodu ile bu flag’ i resetleyerek tekrardan false yaparız. Bu durum unutulmamalıdır. Interrupt durumunu kontrol etmek için kullanılan ve statik olamaya isInterrupted metodu ise, başka bir thread tarafından kontrol edilir ve kontrol edilen thread üzerinde bir interrupt varsa yapılacak işlemler kontrolu yapan thread nesnesi bünyesinde olur. Statşk olmayan isInterrupted() metodu Interrupt flag’ ini temizlemez sadece okur.
Join
Join metodu ile birden fazla thread çalışıyorsa bu thread’lerin birbirlerini beklemesi için kullanılabilir. Mesela bir thread üzerinden çalışan metodumuz çok uzun bir işlem yapıyor olabilir. Veya bir timeout değeri ile işlemin timeout suresinden uzun surmesi durumunda thread sonlandırılabilir. Join metodu bit thread üzerinden çağrıldığında bu metodu çağıran metod thread sonlanıncaya kara bloklanır. Join metodunun overloaded versiyonları ike bekleme zamanı belirlenebilir. Aynı sleep metodunda olduğu gibi join metoduda checked exception istisnası olan InterruptException istisnasını fırlatır.
package net.bsayiner.JoinSample;
public class JoinedClass implements Runnable {
private String[] works = new String[] { "First work", "Second work",
"Third work", "Fourth work" };
@Override
public void run() {
long threadID = Thread.currentThread().getId(); // Çalışan thread numarası alınıyor.
System.out.format("Thread id : %s started.%n", threadID); // Ekrana thread numarası yazılıyor.
for (int i = 0; i < works.length; i++) {
System.out.println(works[i]);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} // End of Try
} // End of For Loop
System.out.format("Thread id : %s terminated.%n", threadID); // Ekrana thread numarası yazılıyor.
} // End of Implemented Method run
} // End of Class JoinedClass
JoinedClass.class
package net.bsayiner.JoinSample;
public class Driver {
public static void main(String[] args) {
long threadID = Thread.currentThread().getId(); // Çalışan thread numarası alınıyor.
System.out.format("Thread id : %s started.%n", threadID); // Ekrana thread numarası yazılıyor.
Thread thread = new Thread(new JoinedClass());
thread.start();
try {
thread.join(); // JoinedClass işini bitirene kadar main thread bloklanıyor.
} catch (InterruptedException e) {
e.printStackTrace();
} // End of try
System.out.format("Thread id : %s terminated.%n", threadID); // Ekrana thread numarası yazılıyor.
} // End of Method main()
} // End of Class Driver
Driver.class
Yukarıdaki Driver.class main metodunda görüldüğü üzere oluşturulan thread üzerinden join metodu çağırılarak , oluşturulan thread’ in işi bitene kadar program bloklanıyor. Aynı sleep metodunda olduğu gibi program bloklandığında dışarıdan bir interrupt istedği gelirse InterruptedException oluşuyor. main metodunda Thread sınıfının static Thread.currentThread() metodu ile o anda çalışmakta olan güncel thread’ in thread numarasını görüyoruz. İsterseniz buraya kadar gördüğümüz konuları pekiştirmek adına bir program yazalım. Bu programda iki tane thread’ imiz olsun. Worker thread ekrana bazı mesajlar yazsın. Main thread’ de belirli bir zaman kadar worker thread’ in işini bitirmesini beklesin. Ancak belilenen timeOut süresi kadar thread işini bitirmezse worker thread sonlandırılsın. Kodlarımız şöyle olacaktır;
package net.bsayiner.TimeOutSample;
public class WorkerClass implements Runnable{
private String[] works = new String[] { "First work", "Second work",
"Third work", "Fourth work" };
@Override
public void run() {
long threadID = Thread.currentThread().getId(); // Çalışan thread numarası alınıyor.
System.out.format("Thread id : %s started.%n", threadID); // Ekrana thread numarası yazılıyor.
for (int i = 0; i < works.length; i++) {
System.out.println(works[i]);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println("Thread isn't finish job");
System.out.format("Thread id : %s started.%n", threadID); // Ekrana thread numarası yazılıyor.
return;
} // End of Try
} // End of For Loop
System.out.format("Thread id : %s started.%n", threadID); // Ekrana thread numarası yazılıyor.
} // End of Implemented Method Run()
} // End of Class WorkerClas
WorkerClas.class
package net.bsayiner.TimeOutSample;
public class Driver {
public static void main(String[] args) {
// Çalışan thread numarası alınıyor.
long threadID = Thread.currentThread().getId();
// Ekrana thread numarası yazılıyor.
System.out.format("Thread id : %s started.%n", threadID);
// Programın baslama anındaki zaman.
long startTime = System.currentTimeMillis();
// ms cinsinden timeOut zamanı.
long timeOut = 2000;
Thread thread = new Thread(new WorkerClass());
thread.start();
while (thread.isAlive()) {
try {
// Worker thread 100ms bekleniyor.
thread.join(100);
} catch (InterruptedException e) {
e.printStackTrace();
} // End of Try
if ((System.currentTimeMillis() - startTime > timeOut)
&& thread.isAlive()) {
thread.interrupt();
}
} // End of While Loop
// Ekrana thread numarası yazılıyor.
System.out.format("Thread id : %s terminated.%n", threadID);
} // End of Method main()
} // End of Class Driver
Driver.class
timeOut değişkenindeki değer değiştirilerek bekleme süresi arttırılıp azalttırılabilir.
Sonucç olarak bu yazımızda Java’ da eş zamanlı programlamaya bir giriş yapmış olduk. Konumuzun devamında görüşmek üzere.