İçindekilerGirişİndex
YukarıİlkÖncekiSonrakiSon
Geriİleri
Yazdır
Mustafa Hadi Dilek
hadi@ulakbim.gov.tr

Java'da Çok Kanallı (Multithreaded) Uygulamalar

Çok görevlilik (multi tasking) özelliği olan işletim sistemleri birden çok görevi eş zamanlı olarak çalıştırabilir. Aslında işlemci aynı anda sadece bir görevi işletebilir. İşletim sistemi belirli aralıklarla işlemciyi bu görevler arasında anahtarlar. Bu bize birden çok program çalışıyormuş izlenimi verir. Burada bir program tek bir görevden oluşabileceği gibi birden çok görevden de oluşabilir. Ancak her görevin program sayacı (program counter), adres alanı (memory) ve tuttuğu dosya numaraları (file handles) gibi sahip olduğu kaynaklardan oluşan bağlamı (context) diğerlerinden ayrıdır.

Çok kanallılık (multi threading) ise çok görevlilikten ayrı olarak görevin kendi içinde eş zamanlı çalışan program parçaları olması şeklinde tanımlanabilir. Bunların hepsi aynı bağlamı (context) paylaşırlar.

Java'da Kanal (Thread) Oluşturma

Java'da kanal (thread) oluşturmak için öncelikle paralel çalışacak olan program parçamızı 'Thread' türü bir sınıf olarak tanımlamamız gerekir. Bu sınıf içinde çağrılmadan çalışacak bir metod olması gerekir. Bu da 'Thread' sınıfında tanımlanmış olan 'run' metodudur. Yapmamız gereken sadece bu metodu değiştirerek (override) istediğimiz kodu bunu içine yerleştirmektir.

public class myThread extends Thread {
  ...
  ... <kendi kanal sınıfımızın diğer metod ve özellikleri>
  ...
  public void run() {
    ...
    ... <kanalın yapacağı işler>
    ...
  }
}

Daha sonra programın bu kanalı gören herhangibir noktasından 'Thread' sınıfının 'start' metodu çalıştırılarak. Thread çalışmaya başlatılabilir. Bu metod kendi içinde (implicitly), 'run' metodunu çağıracak ve dolayısıyla kanala yaptırmak istediğimiz işler yapılmaya başlıyacaktır:

...
...
...
myThread myThreadObj=new myThread();
//--- veya:
//--- Thread myThreadObj=new myThread();
...
...
...
myThreadObj.start();
...
...
...

Kanalın 'run' metodu içinde genellikle gecikmeler içeren döngüler kullanılır:

public class myThread extends Thread {
  ...
  ... <kendi kanal sınıfımızın diğer metod ve özellikleri>
  ...
  public void run() {
    while(true) {
      ...
      ... <belirli aralıklarla yaptırılacak işler>
      ...
      try {
        sleep(1000); // Örneğin her seferinde 1 saniye beklenecek
      }
      catch(InterruptedException e) {
        // Bekleme esnasında kanala işletimi kesme sinyali gönderilirse
        // yapılacak işler burada ele alınacak
      }
      ...
      ... <belirli aralıklarla yaptırılacak işler>
      ...
    }
  }
}

Burada 'Thread' sınıfının 'sleep' metodu ile kanal belli bir süre sonra çağrılmak üzere bekleme kuyruğuna alınır. Bu sayede diğer kanallar da çalışma fırsatı bulurlar. Aynı şekilde 'sleep' yerine aynı sınıfın 'yield' metodu kullanılarak işletimi diğer kanallara geçirmek de mümkündür. Aşağıdaki adreste çok kanallı bir ugulama paralel hareket eden butonlarla örneklenmiştir:

Göster Gizle Kopar Satır Gizle Satır Göster
  1 // Örnek program. Thread oluşturma.
  2 // Yazan: Mustafa Hadi Dilek
  3 import java.awt.*;
  4 import java.awt.event.*;
  5 class thread1 extends Frame
  6 {
  7   public thread1() {
  8     addWindowListener(new WindowAdapter() {
  9       public void windowClosing(WindowEvent e) {
 10         dispose();
 11         System.exit(0);
 12       }
 13     });
 14     
 15     setLayout(null);
 16     Button myObj1=new Button("Button 1");
 17     Button myObj2=new Button("Button 2");
 18     
 19     add(myObj1);
 20     add(myObj2);
 21     myObj1.setSize(70,20);
 22     myObj2.setSize(70,20);
 23     myObj1.setLocation(10,50);
 24     myObj2.setLocation(50,10);
 25     
 26     Thread myThread1=new myThread(myObj1);
 27     Thread myThread2=new myThread(myObj2);
 28     
 29     myThread1.start();
 30     myThread2.start();
 31   }
 32   
 33   public class myThread extends Thread {
 34     public int MoveX=1;
 35     public int MoveY=1;
 36     
 37     private Component myObj;
 38     
 39     public myThread(Component myObj) {
 40       this.myObj=myObj;
 41     }
 42     
 43     public void run() {
 44       while(true) {
 45         Component myContainer=myObj.getParent();
 46         
 47         int BoundWidth =myContainer.getWidth();
 48         int BoundHeight=myContainer.getHeight();
 49         int X=myObj.getX();
 50         int Y=myObj.getY();
 51         int Width =myObj.getWidth();
 52         int Height=myObj.getHeight();
 53         
 54         X+=MoveX;
 55         Y+=MoveY;
 56         myObj.setLocation(X, Y);
 57         if(X+Width  >= BoundWidth)  MoveX= -1*Math.abs(MoveX);
 58         if(X <= 0)                  MoveX=    Math.abs(MoveX);
 59         if(Y+Height >= BoundHeight) MoveY= -1*Math.abs(MoveY);
 60         if(Y <= 0)                  MoveY=    Math.abs(MoveY);
 61         
 62         try {
 63           sleep(10);
 64         }
 65          catch(InterruptedException e) {
 66           System.exit(0);
 67         }
 68       }
 69     }
 70   }
 71   public static void main(String args[]) {
 72     thread1 mainFrame = new thread1();
 73     mainFrame.setSize(400, 400);
 74     mainFrame.setTitle("thread1");
 75     mainFrame.setVisible(true);
 76   }
 77 }

Bazen kanalları yaratırken 'Thread' sınıfını devralmak (inheritance) uygun olmayabilir. Bu durumda 'Runnable' arabirimi (interface) kullanılarak da çalıştırılabilir (runnable) bir sınıf yaratılabilir:

public class myThread implements Runnable {
  ...
  ...
  ...
  public void run() {
    ...
    ...
    ...
    try {
      Thread.sleep(1000);
    }
    catch(InterruptedException e) { }
    ...
    ...
    ...
  }
}

Burada görüldüğü gibi 'sleep' gibi metodlar 'Thread' sınıfından devralınmadığı için ancak 'Thread' sınıfının statik metodları olarak çağrılabilmekte. Görüldüğü gibi bu durumda yarattığımız sınıf 'Thread' sınıfına ait özellik ve metodlara sahip değildir ve bunu kanal gibi belli aralıklarla paralel çalıştıramayız. Ancak ürettiğimiz çalıştırılabilir kesimi yaratacağımız bir kanal nesnesine gömerek tam bir kanal elde edebiliriz. Kanalı yaratıp çalıştırmak şu şekilde mümkün:

Thread myThreadObj=new Thread(new myThread());
...
...
...
myThreadObj.start();
...
...
...

Aşağıda bu tür kanal yaratmaya örnek verilmiştir:

Göster Gizle Kopar Satır Gizle Satır Göster
  1 // Örnek program. 'Runnable' kullanarak thread oluşturma.
  2 // Yazan: Mustafa Hadi Dilek
  3 import java.awt.*;
  4 import java.awt.event.*;
  5 class thread2 extends Frame
  6 {
  7   public thread2() {
  8     addWindowListener(new WindowAdapter() {
  9       public void windowClosing(WindowEvent e) {
 10         dispose();
 11         System.exit(0);
 12       }
 13     });
 14     
 15     setLayout(null);
 16     Button myObj1=new Button("Button 1");
 17     Button myObj2=new Button("Button 2");
 18     
 19     add(myObj1);
 20     add(myObj2);
 21     myObj1.setSize(70,20);
 22     myObj2.setSize(70,20);
 23     myObj1.setLocation(10,50);
 24     myObj2.setLocation(50,10);
 25     
 26     Thread myThread1=new Thread(new myRunnable(myObj1));
 27     Thread myThread2=new Thread(new myRunnable(myObj2));
 28     
 29     myThread1.start();
 30     myThread2.start();
 31   }
 32   
 33   public class myRunnable implements Runnable {
 34     public int MoveX=1;
 35     public int MoveY=1;
 36     
 37     private Component myObj;
 38     
 39     public myRunnable(Component myObj) {
 40       this.myObj=myObj;
 41     }
 42     
 43     public void run() {
 44       while(true) {
 45         Component myContainer=myObj.getParent();
 46         
 47         int BoundWidth =myContainer.getWidth();
 48         int BoundHeight=myContainer.getHeight();
 49         int X=myObj.getX();
 50         int Y=myObj.getY();
 51         int Width =myObj.getWidth();
 52         int Height=myObj.getHeight();
 53         
 54         X+=MoveX;
 55         Y+=MoveY;
 56         myObj.setLocation(X, Y);
 57         if(X+Width  >= BoundWidth)  MoveX= -1*Math.abs(MoveX);
 58         if(X <= 0)                  MoveX=    Math.abs(MoveX);
 59         if(Y+Height >= BoundHeight) MoveY= -1*Math.abs(MoveY);
 60         if(Y <= 0)                  MoveY=    Math.abs(MoveY);
 61         
 62         try {
 63           Thread.sleep(10);
 64         }
 65         catch(InterruptedException e) {
 66           System.exit(0);
 67         }
 68       }
 69     }
 70   }
 71   public static void main(String args[]) {
 72     thread2 mainFrame = new thread2();
 73     mainFrame.setSize(400, 400);
 74     mainFrame.setTitle("thread2");
 75     mainFrame.setVisible(true);
 76   }
 77 }

Kanallar Arası Haberleşme

Kanallar aynı bağlamı paylaşan kod kesimleri olduğu için aynı görev içinde iseler gördükleri global bir bellek alanı mutlaka vardır. Kanallar arası iletişimde bu alanlar kullanılabilir. Aşağıda kanallar arası haberleşmede dört farklı global kullanımı örneklenmiştir:

...
...
public class myClass{
  ...
  ...
  public static int myGlobalA;
  public int myGlobalB;
  ...
  ...

}
...
...
private myClass myClassObj=new myClass();
private int myGlobalC;
...
...
public class myThread extends Thread {
  public int myGlobalD;
  private myThread anotherThread=null;
  ...
  ...
  public void run() {
    ...
    ...
    myClass.myGlobalA++;
    myClassObj.myGlobalB++;
    myGlobalC++;
    if(anotherThread!=null) anotherThread.myGlobalD++;
    ...
    ...
  }
}

Global alan kullanımı bazı durumlarda kanallar arasında sorunlara sebep olmaktadır. Bu sorunlar kanallar arası zaman uyumlama kapsamında ele alındı.

Kanallar Arası Zaman Uyumlama (Synchronization)

Kanallar global bir sayaç kulanan aşağıdaki gibi bir kanal düşünelim:

...
...
private int Counter=0;
...
...
1  public class myThread extends Thread {
2    public void run() {
3      while(true) {
4        if(Counter<10)
5           Counter++;
6        else
7           break;
8 
9        try { sleep(10); }
10       catch(InterruptedException e) { }
11     }
12   }
13 }

Burada tüm kanallardan toplam geçişin 10 olduktan sonra kanalların işletimini durdurmak istiyoruz. Ancak, birbirinden bağımsız çalıştıklarına göre sayaç 9 iken kanallardan biri 4. satırdaki koşulun sağlandığını görerek 5. satıra geçer. Aynı anda 4.satıra gelen başka bir kanal da aynı koşulun sağlandığını görerek 5. satıra geçer. Böylece sayacın değeri önce 10 sonra da 11 olarak programımız hatalı çalışmış olur. Bu tür durumları önlemek için kanallarda işletilecek kritik kesimler aynı anda tek bir kanalın erişebileceği yapıda oluşturulur. Aşağıdaki örnek bu sorunu çözer:

...
...
private int Counter=0;
...
...
public class myThread extends Thread {
  private boolean synchronized IncCounter() {
    if(Counter<10) {
      Counter++;
      return true;
    }
    else return false;
  }

  public void run() {
    while(true) {
      if(!IncCounter()) break;

      try { sleep(10); }
      catch(InterruptedException e) { }
    }
  }
}

Aynı yapı aşağıdaki gibi de kurulabilir:

...
...
private int Counter=0;
...
...
public class myThread extends Thread {

  public void run() {

    while(true) {
      synchronized(Counter) {
        if(Counter<10)
          Counter++;
        else
          break;
      }

      try { sleep(10); }
      catch(InterruptedException e) { }
    }
  }
}

Dosya Listesi

İçindekilerGirişİndex
YukarıİlkÖncekiSonrakiSon
Geriİleri
Yazdır