Skip to content

7. Çalıştırma İş Parçacıkları

7.1. Giriş

Bir uygulama başlatıldığında, bu uygulama "iş parçacığı" olarak adlandırılan bir yürütme akışında çalışır. thread'i modelleyen sınıf, java.lang.Thread sınıfıdır. Bu sınıfın bazı özellikleri ve yöntemleri şunlardır:

currentThread()
şu anda yürütülmekte olan iş parçacığını verir
setName()
iş parçacığının adını belirler
getName()
iş parçacığının adı
isAlive()
iş parçacığının aktif olup olmadığını (true) veya olmadığını (false) belirtir
start()
bir iş parçacığının yürütülmesini başlatır
run()
önceki start yöntemi yürütüldükten sonra otomatik olarak yürütülen yöntem
sleep(n)
bir iş parçacığının çalışmasını n milisaniye boyunca durdurur
join()
engelleyici işlem - bir sonraki komuta geçmek için iş parçacığının bitmesini bekler

En sık kullanılan yapıcılar şunlardır:

Thread()
asenkron bir görev üzerine bir referans oluşturur. Bu görev henüz etkin değildir. Oluşturulan görev, run yöntemine sahip olmalıdır: çoğu zaman Thread'ten türetilmiş bir sınıf kullanılır.
Thread(Runnable object)
Aynı şekilde, ancak Runnable nesnesi parametre olarak geçirilerek run yöntemini uyguluyor.

Bir sınıfın main işlevinin yürütüldüğü ana yürütme iş parçacığının varlığını vurgulayan ilk uygulamaya bir göz atalım:

// iş parçacığı kullanımı

import java.io.*;
import java.util.*;

public class thread1{
    public static void main(String[] arg)throws Exception {
         // geçerli iş parçacığının başlatılması
        Thread main=Thread.currentThread();
         // görüntüleme
        System.out.println("Thread courant : " + main.getName());
         // adını değiştirme
        main.setName("myMainThread");
         // doğrulama
        System.out.println("Thread courant : " + main.getName());

         // sonsuz döngü
        while(true){
        // saati alıyoruz
      Calendar calendrier=Calendar.getInstance();
      String H=calendrier.get(Calendar.HOUR_OF_DAY)+":"
      +calendrier.get(Calendar.MINUTE)+":"
      +calendrier.get(Calendar.SECOND);
             // görüntüleme
            System.out.println(main.getName() + " : " +H);
             // geçici durdurma
            Thread.sleep(1000);
        }//while
    }//main
}//sınıf

Ekran sonuçları:


Thread courant : main
Thread courant : myMainThread
myMainThread : 15:34:9
myMainThread : 15:34:10
myMainThread : 15:34:11
myMainThread : 15:34:12
Terminer le programme de commandes (O/N) ? o

Yukarıdaki örnek şu noktaları göstermektedir:

  • main işlevi bir iş parçacığında sorunsuz bir şekilde çalışır
  • Thread.currentThread() işlevi aracılığıyla bu iş parçacığının özelliklerine erişilebilir
  • sleep yönteminin rolü. Burada, main işlevini çalıştıran iş parçacığı, iki görüntüleme arasında düzenli olarak 1 saniye süreyle uyku moduna geçmektedir.

7.2. Çalıştırma iş parçacıklarının oluşturulması

Farklı yürütme iş parçacıklarında kod parçalarının "eşzamanlı" olarak yürütüldüğü uygulamalar olabilir. thread'lerin eşzamanlı olarak yürütüldüğünü söylediğimizde, genellikle yanlış bir ifade kullanmış oluruz. Hala sıklıkla olduğu gibi, makinede tek bir işlemci varsa, thread'ler bu işlemciyi paylaşır: her biri sırayla kısa bir süre (birkaç milisaniye) boyunca işlemciyi kullanır. Bu durum, yürütme paralelliği yanılsamasını yaratır. Bir thread'e ayrılan süre, varsayılan bir değere sahip olan ancak programlama yoluyla da belirlenebilen önceliği de dahil olmak üzere çeşitli faktörlere bağlıdır. Bir thread işlemciye sahip olduğunda, normalde kendisine ayrılan sürenin tamamı boyunca işlemciyi kullanır. Ancak, süre dolmadan önce işlemciyi serbest bırakabilir:

  • bir olayı bekleyerek (wait, join)
  • belirli bir süre boyunca uyku moduna geçerek (sleep)
  • Bir T iş parçacığı çeşitli şekillerde oluşturulabilir
    • Thread sınıfından türetilerek ve bu sınıftaki run yöntemini yeniden tanımlayarak.
    • bir sınıfta Runnable arayüzünü uygulayarak ve new Thread(Runnable) yapıcı işlevini kullanarak. Runnable, yalnızca tek bir yöntem tanımlayan bir arayüzdür: public void run(). Dolayısıyla, yukarıdaki yapıcı argümanı, bu run yöntemini uygulayan herhangi bir sınıf örneğidir.

Aşağıdaki örnekte, iş parçacıkları Thread sınıfından türetilen isimsiz bir sınıf kullanılarak oluşturulmuştur:

            // i iş parçacığı oluşturuluyor
            tâches[i]=new Thread() {
          public void run() {
            affiche();
        }
      };//görev tanımı[i]

run yöntemi burada sadece affiche yöntemine yönlendirme yapmaktadır.

  • T iş parçacığının yürütülmesi, T.start() tarafından başlatılır: bu yöntem Thread sınıfına aittir ve bir dizi başlatma işlemini gerçekleştirdikten sonra, Thread sınıfının veya Runnable arayüzünün run yöntemini otomatik olarak başlatır. T.start() komutunu çalıştıran program, T görevinin bitmesini beklemez: hemen bir sonraki komuta geçer. Böylece paralel olarak çalışan iki görev ortaya çıkar. Bu görevler, gerçekleştirilmesi gereken ortak işin ne aşamada olduğunu öğrenmek için genellikle birbirleriyle iletişim kurabilmelidir. Bu, iş parçacıklarının senkronizasyon sorunudur.
  • Başlatıldıktan sonra, thread bağımsız olarak çalışır. Yürütmekte olduğu run işlevi işini bitirdiğinde durur.
  • T.join() işlevi ile T iş parçacığının yürütülmesinin tamamlanmasını bekleyebiliriz. Burada bir engelleyici komut söz konusudur: bu komutu çalıştıran program, T iş parçacığı işini bitirene kadar beklemede kalır. Bu da bir senkronizasyon yöntemidir.

Şu programı inceleyelim:

// iş parçacıklarının kullanımı

import java.io.*;
import java.util.*;

public class thread2{
    public static void main(String[] arg) {
         // geçerli iş parçacığını başlatma
        Thread main=Thread.currentThread();
         // mevcut iş parçacığına ad veriliyor
        main.setName("myMainThread");
         // main işlevinin başlangıcı
        System.out.println("début du thread " +main.getName());

         // çalışma iş parçacıklarının oluşturulması
        Thread[] tâches=new Thread[5];
        for(int i=0;i<tâches.length;i++){
             // i iş parçacığı oluşturuluyor
            tâches[i]=new Thread() {
          public void run() {
            affiche();
        }
      };//görevler[i]
             // iş parçacığının adı belirleniyor
            tâches[i].setName(""+i);
             // i iş parçacığının yürütülmesi başlatılıyor
            tâches[i].start();
        }//for

         // main işlevinin sonu
        System.out.println("fin du thread " +main.getName());
    }//Ana iş parçacığı

    public static void affiche() {
         // saati alınıyor
    Calendar calendrier=Calendar.getInstance();
    String H=calendrier.get(Calendar.HOUR_OF_DAY)+":"
      +calendrier.get(Calendar.MINUTE)+":"
      +calendrier.get(Calendar.SECOND);
         // çalıştırma başlangıcı görüntüleniyor
        System.out.println("Début d'exécution de la méthode affiche dans le Thread " + 
        Thread.currentThread().getName()+ " : " + H);
         // 1 saniye boyunca uyku moduna geçme
        try{
        Thread.sleep(1000);
    }catch (Exception ex){}
     // saati alıyoruz
    calendrier=Calendar.getInstance();    
    H=calendrier.get(Calendar.HOUR_OF_DAY)+":"
      +calendrier.get(Calendar.MINUTE)+":"
      +calendrier.get(Calendar.SECOND);
         // çalışma sonu mesajı görüntülenir
        System.out.println("Fin d'exécution de la méthode affiche dans le Thread " 
    +Thread.currentThread().getName()+ " : " + H);
    }// görüntüleme
}//sınıf

main işlevini çalıştıran ana iş parçacığı, affiche statik yöntemini çalıştırmakla görevli 5 iş parçacığı daha oluşturur. Sonuçlar şöyledir:


début du thread myMainThread
Début d'exécution de la méthode affiche dans le Thread 0 : 15:48:3
fin du thread myMainThread
Début d'exécution de la méthode affiche dans le Thread 1 : 15:48:3
Début d'exécution de la méthode affiche dans le Thread 2 : 15:48:3
Début d'exécution de la méthode affiche dans le Thread 3 : 15:48:3
Début d'exécution de la méthode affiche dans le Thread 4 : 15:48:3
Fin d'exécution de la méthode affiche dans le Thread 0 : 15:48:4
Fin d'exécution de la méthode affiche dans le Thread 1 : 15:48:4
Fin d'exécution de la méthode affiche dans le Thread 2 : 15:48:4
Fin d'exécution de la méthode affiche dans le Thread 3 : 15:48:4
Fin d'exécution de la méthode affiche dans le Thread 4 : 15:48:4

Bu sonuçlar oldukça aydınlatıcıdır:

  • öncelikle, bir iş parçacığının yürütülmesinin başlatılmasının engelleyici olmadığı görülüyor. main yöntemi, 5 iş parçacığının yürütülmesini paralel olarak başlattı ve onlardan önce kendi yürütülmesini tamamladı.
             // i iş parçacığının yürütülmesini başlat
            tâches[i].start();

iş parçacığı tâches[i]'in yürütülmesini başlatır, ancak bu işlem tamamlandıktan sonra, iş parçacığının yürütülmesinin bitmesini beklemeden yürütme hemen sonraki komutla devam eder.

  • Oluşturulan tüm iş parçacıkları affiche yöntemini çalıştırmalıdır. Çalıştırma sırası öngörülemez. Örnekte çalıştırma sırası iş parçacıklarının başlatılma sırasını takip ediyor gibi görünse de, bundan genel bir sonuç çıkarılamaz. İşletim sisteminde burada 6 iş parçacığı ve bir işlemci bulunmaktadır. İşletim sistemi, kendi kurallarına göre işlemciyi bu 6 iş parçacığına dağıtacaktır.
  • Sonuçlarda sleep yönteminin bir sonucu görülmektedir. Örnekte, affiche yöntemini ilk olarak 0 numaralı iş parçacığı yürütmektedir. Çalıştırma başlangıç mesajı görüntülenir, ardından sleep yöntemini çalıştırır ve bu yöntem iş parçacığını 1 saniye boyunca askıya alır. Böylece işlemciyi kaybeder ve işlemci başka bir iş parçacığı için kullanılabilir hale gelir. Örnekte, işlemciyi 1 numaralı iş parçacığının alacağı görülmektedir. İş parçacığı 1, diğer iş parçacıklarıyla aynı yolu izleyecektir. İş parçacığı 0’ın 1 saniyelik bekleme süresi sona erdiğinde, yürütmesi devam edebilir. Sistem ona işlemciyi tahsis eder ve iş parçacığı, affiche yönteminin yürütmesini tamamlayabilir.

Programımızı değiştirerek main yöntemini şu komutlarla sonlandıralım:

         // ana işlevin sonu
        System.out.println("fin du thread " +main.getName());
     // uygulama durduruluyor
    System.exit(0);

Yeni programın çalıştırılması sonucunda şu çıktı elde edilir:


début du thread myMainThread
Début d'exécution de la méthode affiche dans le Thread 0 : 16:5:45
Début d'exécution de la méthode affiche dans le Thread 1 : 16:5:45
Début d'exécution de la méthode affiche dans le Thread 2 : 16:5:45
Début d'exécution de la méthode affiche dans le Thread 3 : 16:5:45
fin du thread myMainThread
Début d'exécution de la méthode affiche dans le Thread 4 : 16:5:45

main yöntemi şu komutu çalıştırır çalıştırmaz:

    System.exit(0);

komutunu yürütür yürütmez, sadece main iş parçacığını değil, uygulamanın tüm iş parçacıklarını durdurur. main yöntemi, kendi çalışmasını sonlandırmadan önce oluşturduğu iş parçacıklarının çalışmasının bitmesini beklemek isteyebilir. Bu, Thread sınıfındaki join yöntemi ile yapılabilir:

     // tüm iş parçacıklarının tamamlanması bekleniyor
        for(int i=0;i<tâches.length;i++){
            // i iş parçacığı bekleniyor
            tâches[i].join();
    }//for  

         // ana işlevin sonu
        System.out.println("fin du thread " +main.getName());
     // uygulama durduruldu
    System.exit(0);

Böylece şu sonuçlar elde edilir:

début du thread myMainThread
Début d'exécution de la méthode affiche dans le Thread 0 : 16:11:9
Début d'exécution de la méthode affiche dans le Thread 1 : 16:11:9
Début d'exécution de la méthode affiche dans le Thread 2 : 16:11:9
Début d'exécution de la méthode affiche dans le Thread 3 : 16:11:9
Début d'exécution de la méthode affiche dans le Thread 4 : 16:11:9
Fin d'exécution de la méthode affiche dans le Thread 0 : 16:11:10
Fin d'exécution de la méthode affiche dans le Thread 1 : 16:11:10
Fin d'exécution de la méthode affiche dans le Thread 2 : 16:11:10
Fin d'exécution de la méthode affiche dans le Thread 3 : 16:11:10
Fin d'exécution de la méthode affiche dans le Thread 4 : 16:11:10
fin du thread myMainThread

7.3. İş parçacıklarının önemi

Artık varsayılan bir iş parçacığının varlığını ortaya çıkardık; bu iş parçacığı Main yöntemini yürütür ve başka iş parçacıkları nasıl oluşturulacağını da öğrendik. Şimdi iş parçacıklarının bizim için ne kadar önemli olduğuna ve bunları burada neden ele aldığımıza değinelim. İş parçacıklarının kullanımına son derece uygun bir uygulama türü vardır: İnternet’teki istemci-sunucu uygulamaları. Böyle bir uygulamada, S1 makinesinde bulunan bir sunucu, C1, C2, ..., Cn gibi uzak makinelerde bulunan istemcilerin isteklerine yanıt verir.

Image

Her gün bu şemaya uyan internet uygulamalarını kullanıyoruz: web hizmetleri, e-posta, forumlara göz atma, dosya aktarımı... Yukarıdaki şemada, S1 sunucusunun Ci istemcilerine aynı anda hizmet vermesi gerekiyor. Müşterilerine dosya ileten bir FTP (Dosya Aktarım Protokolü) sunucusunu örnek alırsak, bir dosya aktarımının bazen birkaç saat sürebileceğini biliriz. Elbette bir müşterinin bu kadar uzun süre boyunca sunucuyu tek başına işgal etmesi söz konusu olamaz. Genellikle yapılan şey, sunucunun müşteri sayısı kadar yürütme iş parçacığı oluşturmasıdır. Her iş parçacığı, belirli bir müşteriyle ilgilenmekle görevlendirilir. İşlemci, makinedeki tüm aktif iş parçacıkları arasında döngüsel olarak paylaşıldığından, sunucu her bir müşteriyle biraz zaman geçirir ve böylece hizmetin eşzamanlılığını sağlar.

Image

7.4. Grafik saat

Bir saat ve saati durdurmak veya yeniden başlatmak için bir düğme içeren bir pencere görüntüleyen aşağıdaki uygulamayı ele alalım:

Saatin çalışması için, bir işlemin her saniye saati değiştirmesi gerekir. Aynı zamanda, pencerede meydana gelen olayları da izlemek gerekir: kullanıcı "Durdur" düğmesine tıkladığında, saat durdurulmalıdır. Burada iki paralel ve eşzamansız görev söz konusudur: kullanıcı herhangi bir anda tıklayabilir.

Saatin henüz başlatılmadığı ve kullanıcının "Başlat" düğmesine tıkladığı anı ele alalım. Burada klasik bir olay söz konusudur ve pencerenin çalıştığı iş parçacığının bir yönteminin saati yönetebileceği düşünülebilir. Ancak, grafik uygulamasının bir yöntemi yürütülürken, uygulamanın iş parçacığı artık grafik arayüzündeki olayları dinlemiyor. Bu olaylar meydana gelir ve o anda yürütülmekte olan yöntem tamamlandığında uygulama tarafından işlenmek üzere bir kuyruğa alınır. Saat örneğimizde, yöntem her zaman çalışır durumda olacaktır; çünkü yalnızca "Durdur" düğmesine tıklanması onu durdurabilir. Ancak bu olay, yöntem tamamlandığında işlenecektir. Bir döngü içinde kalıyoruz.

Bu sorunun çözümü, kullanıcı "Başlat" düğmesine tıkladığında saatin yönetilmesi için bir görev başlatılması, ancak uygulamanın pencerede meydana gelen olayları dinlemeye devam edebilmesi olabilir. Böylece, paralel olarak çalışan iki ayrı görevimiz olur:

  • saatin yönetimi
  • penceredeki olayları dinleme

Grafik saatimize geri dönelim:

no
tür
ad
rol
1
JTextField (Düzenlenebilir=false)
txtHorloge
saati gösterir
2
JButton
btnGoStop
saati durdurur veya başlatır

JBuilder ile oluşturulan uygulamanın kaynak kodu şöyledir:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;

public class interfaceHorloge extends JFrame {
  JPanel contentPane;
  JTextField txtHorloge = new JTextField();
  JButton btnGoStop = new JButton();

  // örnek öznitelikleri
  boolean finHorloge=true;

   //Çerçeveyi oluştur
  public interfaceHorloge() {
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }

  private void runHorloge(){
    // durdurulana kadar döngü devam eder
    while( ! finHorloge){
       // saati alıyoruz
      Calendar calendrier=Calendar.getInstance();
      String H=calendrier.get(Calendar.HOUR_OF_DAY)+":"
      +calendrier.get(Calendar.MINUTE)+":"
      +calendrier.get(Calendar.SECOND);
       // T alanına yazdır
      txtHorloge.setText(H);
       // bir saniye bekliyoruz
      try{
        Thread.sleep(1000);
      } catch (Exception e){
        // hata ile çıkış
        System.exit(1);
      }//deneme
    }// while
  }// runHorloge

   //Bileşeni başlat
  private void jbInit() throws Exception  {
...................
  }

   //Değiştirildi, böylece pencere kapatıldığında çıkabiliriz
  protected void processWindowEvent(WindowEvent e) {
.............
  }

  void btnGoStop_actionPerformed(ActionEvent e) {
    // saati başlatır/durdurur
     // düğmenin etiketini alıyoruz
    String libellé=btnGoStop.getText();
    // başlatmak mı?
    if(libellé.equals("Lancer")){
      // saatin çalışacağı iş parçacığını oluşturur
      Thread thHorloge=new Thread(){
        public void run(){
          runHorloge();
        }
      };//iş parçacığı tanımlama
       // iş parçacığının başlatılmasına izin verilir
      finHorloge=false;
       // düğmenin etiketini değiştiriyoruz
      btnGoStop.setText("Arrêter");
      // iş parçacığını başlatıyoruz
      thHorloge.start();
      // son
      return;
    }//if
    // durdur
    if(libellé.equals("Arrêter")){
      // iş parçacığına durması söylenir
      finHorloge=true;
       // düğmenin etiketini değiştiriyoruz
      btnGoStop.setText("Lancer");
       // son
      return;
    }//if
  }
} 

Kullanıcı "Başlat" düğmesine tıkladığında, isimsiz bir sınıf kullanılarak bir iş parçacığı oluşturulur:

      Thread thHorloge=new Thread(){
        public void run(){
          runHorloge();
        }

İş parçacığının run yöntemi, uygulamanın runHorloge yöntemine yönlendirir. Bu işlem tamamlandıktan sonra iş parçacığı başlatılır:

      // iş parçacığını başlat
      thHorloge.start();

Ardından runHorloge yöntemi çalıştırılır:

  private void runHorloge(){
    // durdurulana kadar döngüye girilir
    while( ! finHorloge){
       // saati alıyoruz
      Calendar calendrier=Calendar.getInstance();
      String H=calendrier.get(Calendar.HOUR_OF_DAY)+":"
      +calendrier.get(Calendar.MINUTE)+":"
      +calendrier.get(Calendar.SECOND);
       // T alanına yazdır
      txtHorloge.setText(H);
       // bir saniye bekliyoruz
      try{
        Thread.sleep(1000);
      } catch (Exception e){
        // hata ile çıktı
        System.exit(1);
      }//deneme
    }// while
  }// runHorloge

Yöntemin çalışma prensibi şöyledir:

  1. txtHorloge metin kutusunda geçerli saati gösterir
  2. 1 saniye bekler
  3. önceden finHorloge boole değerini test ettikten sonra 1. adıma geri döner; bu değer, kullanıcı Arrêter düğmesine tıkladığında true olarak ayarlanır.

7.5. Saat applet'i

Önceki grafik uygulamasını her zamanki yöntemle bir applet'e dönüştürüyoruz ve aşağıdaki HTML appletHorloge.htm belgesini oluşturuyoruz:

<html>
  <head>
    <title>Applet Horloge</title>
  </head>
  <body>
      <h2>Une applet horloge</h2>
    <applet
      code="appletHorloge.class"
      width="150"
      height="130"
    ></applet>
    </center>
  </body>
</html>

Bu belgeyi IE'e çift tıklayarak doğrudan yüklediğimizde, aşağıdaki ekran görüntülenir:

Image

Bu örnekte, applet için gerekli tüm öğeler aynı klasörde bulunmaktadır:

E:\data\serge\Jbuilder\horloge\1>dir
13/06/2002  12:17                3 174 appletHorloge.class
13/06/2002  12:17                  658 appletHorloge$1.class
13/06/2002  12:17                  512 appletHorloge$2.class
13/06/2002  12:20                  245 appletHorloge.htm

Applet'imiz geliştirilebilir. Applet yüklendiğinde init yönteminin çalıştırıldığını, ardından varsa start yönteminin de çalıştırıldığını belirtmiştik. Ayrıca, kullanıcı sayfadan ayrıldığında, varsa stop yöntemi yürütülür. Kullanıcı applet sayfasına geri döndüğünde, start yöntemi yeniden çağrılır. Bir applet görsel animasyon iş parçacıkları kullandığında, iş parçacıklarını başlatmak ve durdurmak için genellikle applet’in start ve stop yöntemleri kullanılır. Animasyon gizliyken bir görsel animasyon iş parçacığının arka planda çalışmaya devam etmesinin bir anlamı yoktur.

Bu nedenle, applet’imize aşağıdaki start ve stop yöntemlerini ekliyoruz:

  public void stop(){
    // sayfa gizlenmiş
     // takip
    System.out.println("Page stop");
     // sayfa gizlendi - iş parçacığı durduruluyor
    finHorloge=true;
  }

  public void start(){
     // sayfa yeniden görünüyor
     // takip
    System.out.println("Page start");
     // gerekirse yeni bir zamanlayıcı iş parçacığı başlatılıyor
    if(btnGoStop.getText().equals("Arrêter")){
      // etiket değiştiriliyor
      btnGoStop.setText("Lancer");
       // ve kullanıcı üzerine tıklamış gibi davranılır
      btnGoStop_actionPerformed(null);
    }//if
  }//başlat

Ayrıca, iş parçacığının run yöntemine, ne zaman başladığını ve bittiğini öğrenmek için bir izleme ekledik:

  private void runHorloge(){
    // takip
    System.out.println("Thread horloge lancé");
    // durdurulana kadar döngüye devam ediyoruz
    while( ! finHorloge){
       // saati alıyoruz
      Calendar calendrier=Calendar.getInstance();
      String H=calendrier.get(Calendar.HOUR_OF_DAY)+":"
      +calendrier.get(Calendar.MINUTE)+":"
      +calendrier.get(Calendar.SECOND);
       // T alanına yazdır
      txtHorloge.setText(H);
       // bir saniye bekliyoruz
      try{
        Thread.sleep(1000);
      } catch (Exception e){
        // hata ile çıkış
        return;
      }//deneme
    }// while
    // takip
    System.out.println("Thread horloge terminé");
  }// runHorloge

Şimdi AppletViewer ile uygulamayı çalıştırıyoruz:

E:\data\serge\Jbuilder\horloge\1>appletviewer appletHorloge.htm
Page start    // applet başlatıldı - sayfa görüntülendi
Thread horloge lancé    // buna bağlı olarak iş parçacığı başlatıldı
Page stop    // applet simge durumuna getirildi
Thread horloge terminé    // iş parçacığı buna göre durduruldu
Page start    // applet yeniden görüntülendi
Thread horloge lancé    // iş parçacığı yeniden başlatıldı
Thread horloge terminé    // durdur düğmesine basıldı
Thread horloge lancé    // başlat düğmesine basıldı
Page stop    // applet simge modunda    e
Thread horloge terminé    // buna bağlı olarak iş parçacığı durduruldu
Page start    // applet yeniden görüntüleniyor
Thread horloge lancé    // buna bağlı olarak iş parçacığı yeniden başlatıldı

AppletViewer ile, AppletViewer penceresi görünür durumdayken start olayı gerçekleşir ve pencere simge durumuna getirildiğinde ise stop olayı gerçekleşir. Yukarıdaki sonuçlar, HTML belgesi gizlendiğinde, aktif durumdaysa iş parçacığının düzgün bir şekilde durdurulduğunu göstermektedir.

7.6. Görevlerin senkronizasyonu

Önceki örneğimizde iki görev vardı:

  • uygulamanın kendisi tarafından temsil edilen ana görev
  • saat işlevinden sorumlu görev

İki görev arasındaki koordinasyon, saat iş parçacığını durdurmak için bir boole değeri ayarlayan ana görev tarafından sağlanıyordu. Şimdi, görevlerin ortak kaynaklara eşzamanlı erişimi sorununu ele alacağız; bu sorun "kaynak paylaşımı" olarak da bilinir. Bunu açıklamak için önce bir örnek inceleyeceğiz.

7.6.1. Senkronize edilmemiş bir sayım

Aşağıdaki grafik arayüzü ele alalım:

Image

no
tür
ad
rol
1
JTextField
txtAGénérer
oluşturulacak iş parçacığı sayısını belirtir
2
JTextfield
(düzenlenemez)
txtGénéres
oluşturulan iş parçacığı sayısını gösterir
3
JTextField
(düzenlenemez)
txtStatus
karşılaşılan hatalar ve uygulamanın kendisi hakkında bilgi verir
4
JButton
btnGénérer
iş parçacıklarının oluşturulmasını başlatır

Uygulamanın işleyişi şu şekildedir:

  • kullanıcı, 1 numaralı alana oluşturulacak iş parçacığı sayısını girer
  • 4 numaralı düğmeyle bu iş parçacıklarının oluşturulmasını başlatır
  • iş parçacıkları 2 numaralı alanın değerini okur, bu değeri bir artırır ve yeni değeri görüntüler. Başlangıçta bu alan 0 değerini içerir.

Oluşturulan iş parçacıkları bir kaynağı paylaşır: 2 numaralı alanın değeri. Burada, böyle bir durumda karşılaşılan sorunları göstermeyi amaçlıyoruz. İşte bir çalışma örneği:

Image

Görüldüğü gibi, 1000 iş parçacığının oluşturulmasını talep ettik, ancak sadece 7 tanesi sayıldı. Uygulamanın ilgili kodu şöyledir:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class interfaceSynchro extends JFrame {
  JPanel contentPane;
  JLabel jLabel1 = new JLabel();
  JTextField txtAGénérer = new JTextField();
  JButton btnGénérer = new JButton();
  JTextField txtStatus = new JTextField();
  JTextField txtGénérés = new JTextField();
  JLabel jLabel2 = new JLabel();

   // örnek değişkenleri
    Thread[] tâches=null;   // iş parçacıkları
    int[] compteurs=null;   // sayaçlar

   //Çerçeveyi oluştur
  public interfaceSynchro() {
..........
  }

   //Bileşeni başlatma
  private void jbInit() throws Exception  {
......................
  }

   //Değiştirildi, böylece pencere kapatıldığında çıkabiliriz
  protected void processWindowEvent(WindowEvent e) {
..................
  }

  void btnGénérer_actionPerformed(ActionEvent e) {
     //iş parçacıklarının oluşturulması

     // oluşturulacak iş parçacığı sayısını okuma
    int nbThreads=0;
    try{
      // iş parçacığı sayısını içeren alan okunuyor
      nbThreads=Integer.parseInt(txtAGénérer.getText().trim());
      // pozitif >
      if(nbThreads<=0) throw new Exception();
    }catch(Exception ex){
       //hata
      txtStatus.setText("Nombre invalide");
       // yeniden başlıyor
      txtAGénérer.requestFocus();
      return;
    }//yakalama

     // başlangıçta iş parçacığı oluşturulmadı
    txtGénérés.setText("0");  // iş sayacı 0'da

     // iş parçacıkları oluşturuluyor ve başlatılıyor
    tâches=new Thread[nbThreads];
    compteurs=new int[nbThreads];
    for(int i=0;i<tâches.length;i++){
      // i iş parçacığı oluşturuluyor
      tâches[i]=new Thread() {
          public void run() {
          incrémente();
        }
      };//i iş parçacığı
       // adı tanımlanıyor
      tâches[i].setName(""+i);
      // çalıştırılıyor
      tâches[i].start();
    }//for
  }//üret

   // artırma
  private void incrémente(){
     // iş parçacığı numarasını alın
    int iThread=0;
    try{
      iThread=Integer.parseInt(Thread.currentThread().getName());
    }catch(Exception ex){}
     // görev sayacının değerini okunur
    try{
      compteurs[iThread]=Integer.parseInt(txtGénérés.getText());
    } catch (Exception e){}
     // artırılır
    compteurs[iThread]++;

     // 100 milisaniye beklenir - iş parçacığı bu sırada işlemciyi kaybeder
    try{
      Thread.sleep(100);
    } catch (Exception e){
      System.exit(0);
    }

     // yeni sayacı görüntülenir
    txtGénérés.setText("");
    txtGénérés.setText(""+compteurs[iThread]);
    // izleme
    System.out.println("Thread " + iThread + " : " + compteurs[iThread]);
  }// artırılır

}// sınıf

Kodu detaylı olarak inceleyelim:

  • Pencere, iki örnek değişkeni tanımlar:
   // örnek değişkenleri
    Thread[] tâches=null;   // iş parçacıkları
    int[] compteurs=null;   // sayaçlar

tâches dizisi, oluşturulan iş parçacıklarının dizisi olacaktır. compteurs dizisi, tâches dizisiyle ilişkilendirilecektir. Her görev, grafik arayüzündeki txtGénérés alanının değerini almak için kendine ait bir sayaca sahip olacaktır.

  • Générer düğmesine tıklandığında, btnGénérer_actionPerformed yöntemi çalıştırılır.
  • Bu yöntem, ilk olarak oluşturulacak iş parçacığı sayısını alır. Gerekirse, bu sayı kullanılamazsa bir hata bildirilir. Ardından, istenen iş parçacıklarını oluşturur; bu sırada iş parçacıklarının referanslarını bir diziye kaydeder ve her birine bir numara atar. Oluşturulan iş parçacıklarının run yöntemi, sınıfın incrémente yöntemine yönlendirir. Tüm iş parçacıkları başlatılır (start). İş parçacıklarıyla ilişkili sayaç dizisi de oluşturulur.
  • incrémente yöntemi:
  • txtGénérés alanının mevcut değerini okur ve bunu o anda çalışan iş parçacığına ait sayaca kaydeder
  • 100 ms boyunca durur; bu, işlemciyi kasıtlı olarak boşta bırakmak içindir
  • txtGénérés alanındaki yeni değeri görüntüler

Şimdi iş parçacığı sayımının neden yanlış olduğunu açıklayalım. Oluşturulacak 2 iş parçacığı olduğunu varsayalım. Bunlar öngörülemez bir sırayla çalışır. Bunlardan biri ilk olarak iş parçacığı geçip sayacın 0 değerini okur. Ardından sayacı 1'e ayarlar ancak pencereye yazmaz: kasıtlı olarak 100 ms süreyle durur. Böylece işlemciyi kaybeder ve işlemci başka bir iş parçacığına verilir. Bu iş parçacığı da öncekiyle aynı şekilde çalışır: pencerenin sayacını okur ve orada hâlâ bulunan 0 değerini alır. Sayacı 1'e değiştirir ve bir önceki iş parçacığı gibi 100 ms süreyle durur. İşlemci daha sonra tekrar ilk iş parçacığına verilir: bu iş parçacığı penceredeki sayaca 1 değerini yazar ve sonlanır. İşlemci şimdi ikinci iş parçacığına verilir ve o da 1 yazacaktır. Sonuç yanlış olur.

Sorun nereden kaynaklanıyor? İkinci iş parçacığı, penceredeki sayacı güncelleme işini tamamlamadan önce ilk iş parçacığının kesintiye uğraması nedeniyle yanlış bir değer okudu. Bu da bizi bir programdaki kritik kaynak ve kritik bölüm kavramlarına götürür:

  • kritik kaynak, aynı anda yalnızca bir iş parçacığı tarafından tutulabilen bir kaynaktır. Burada kritik kaynak, penceredeki 2 sayacınıdır.
  • Bir programın kritik bölümü, bir iş parçacığının yürütme akışında kritik bir kaynağa eriştiği komut dizisidir. Bu kritik bölüm boyunca kaynağa erişimin sadece o iş parçacığına ait olduğundan emin olunmalıdır.

7.6.2. Yöntemle senkronize sayım

Önceki örnekte, her iş parçacığı pencerenin incrémente yöntemini yürütüyordu. incrémente yöntemi şu şekilde tanımlanmıştı:

    private void incremente()

Şimdi bunu farklı bir şekilde tanımlıyoruz:

   // artırma
  private synchronized void incrémente(){

synchronized anahtar sözcüğü, incrémente yöntemini aynı anda yalnızca bir iş parçacığının çalıştırabileceği anlamına gelir. Aşağıdaki notları ele alalım:

  • btnGénérer_actionPerformed içinde iş parçacıklarını oluşturan F pencere nesnesi
  • F tarafından oluşturulan T1 ve T2 adlı iki iş parçacığı

Her iki iş parçacığı da F tarafından oluşturulur ve başlatılır. Dolayısıyla her ikisi de F.run yöntemini çalıştıracaktır. T1'in ilk ulaştığını varsayalım. Bu iş parçacığı, F.run'i çalıştırır, ardından senkronize bir yöntem olan F.incremente'i çalıştırır. Sayaçtan 0 değerini okur, sayacı bir artırır ve ardından 100 ms boyunca durur. İşlemci daha sonra T2’e verilir; bu da sırasıyla F.run’i ve ardından F.incremente’i yürütür. Bu noktada işlem tıkanır; çünkü T1 iş parçacığı F.incremente'i yürütmektedir ve synchronized anahtar sözcüğü, F.incremente'i aynı anda yalnızca bir iş parçacığının yürütebilmesini sağlar. Böylece T2, sayaç değerini okuyamadan sırayla işlemciyi kaybeder. 100 ms'nin sonunda, T1 işlemciyi geri alır, sayacın değerini 1 olarak görüntüler, F.incremente'ten ve ardından F.run'ten çıkar ve sonlanır. T2 daha sonra işlemciyi geri alır ve bu sefer F.incremente'i çalıştırabilir, çünkü T1 artık bu yöntemi yürütmüyor. T2 daha sonra sayacın 1 değerini okur, sayacı bir artırır ve 100 ms boyunca durur. 100 ms sonra işlemciyi geri alır, sayacın 2 değerini görüntüler ve kendisi de sonlanır. Bu sefer elde edilen değer doğrudur. İşte test edilmiş bir örnek:

Image

7.6.3. Bir nesne tarafından senkronize edilen sayım

Önceki örnekte, txtGénérés sayacına erişim bir yöntem aracılığıyla senkronize edilmiştir. İş parçacıklarını oluşturan pencereye F deniyorsa, F.incremente yönteminin, aynı anda yalnızca tek bir iş parçacığı tarafından kullanılması gereken bir kaynağı temsil ettiği de söylenebilir. Dolayısıyla bu, kritik bir kaynaktır. Bu kaynağa senkronize erişim, synchronized anahtar sözcüğü ile sağlanmıştır:

    private synchronized void incrémente()

Kritik kaynağın F nesnesinin kendisi olduğu da söylenebilir. Bu, kritik kaynağın F.incremente olduğu duruma kıyasla daha katıdır. Nitekim, ikinci durumda, T1 adlı bir iş parçacığı F.incremente’i çalıştırırsa, T2 iş parçacığı F.incremente'i çalıştıramaz, ancak F nesnesinin senkronize olsun ya da olmasın başka bir yöntemini çalıştırabilir. F nesnesinin kendisi kritik kaynak olması durumunda, bir T1 iş parçacığı bu nesnenin senkronize edilmiş bir bölümünü çalıştırdığında, nesnenin diğer tüm senkronize bölümleri diğer iş parçacıkları için erişilemez hale gelir. Dolayısıyla, bir T1 iş parçacığı senkronize F.incremente yöntemini çalıştırırsa, bir T2 iş parçacığı sadece F.incremente'i değil, aynı zamanda F nesnesinin diğer tüm senkronize bölümlerini de çalıştıramaz, hiçbir iş parçacığı bunları kullanmıyor olsa bile. Dolayısıyla bu, daha kısıtlayıcı bir yöntemdir.

Pencerenin kritik kaynak haline geldiğini varsayalım. Bu durumda şu şekilde yazacağız:

   // artırma
  private void incrémente(){
     // kritik bölüm
    synchronized(this){
       // iş parçacığı numarasını alıyoruz
      int iThread=0;
      try{
        iThread=Integer.parseInt(Thread.currentThread().getName());
      }catch(Exception ex){}
       // iş sayacının değerini okuma
      try{
        compteurs[iThread]=Integer.parseInt(txtGénérés.getText());
      } catch (Exception e){}
       // artırılır
      compteurs[iThread]++;

       // 100 milisaniye beklenir – iş parçacığı bu sırada işlemciyi kaybeder
      try{
        Thread.sleep(100);
      } catch (Exception e){
        System.exit(0);
      }

       // yeni sayacı görüntülenir
      txtGénérés.setText("");
      txtGénérés.setText(""+compteurs[iThread]);
    }//synchronized
  }// artırılır

Tüm iş parçacıkları, senkronizasyon için this penceresini kullanır. Çalıştırıldığında, daha önce olduğu gibi aynı doğru sonuçlar elde edilir. Aslında, tüm iş parçacıkları tarafından bilinen herhangi bir nesne üzerinde senkronizasyon sağlanabilir. Örneğin, aynı sonuçları veren başka bir yöntem şöyledir:

   // örnek değişkenleri
    Thread[] tâches=null;   // iş parçacıkları
    int[] compteurs=null;   // sayaçlar
    Object synchro=new Object(); // bir iş parçacığı senkronizasyon nesnesi

   // artırma
  private void incrémente(){
     // kritik bölüm
    synchronized(synchro){
..............
    }//senkronize
  }// artırma

Pencere, iş parçacıklarının senkronizasyonu için kullanılacak Object türünde bir nesne oluşturur. Bu yöntem, this nesnesi üzerinden senkronizasyon yapan yöntemden daha iyidir, çünkü daha az kısıtlayıcıdır. Burada, bir T1 iş parçacığı incrémente nesnesinin senkronize edilmiş bölümündeyken,bir T2 iş parçacığı, aynı this nesnesinin ancak synchro dışında başka bir nesneyle senkronize edilen başka bir senkronize bölümünü çalıştırmak isterse, bunu yapabilir.

7.6.4. Olaylara dayalı senkronizasyon

Bu sefer, bir iş parçacığına kritik bölüme girip giremeyeceğini belirtmek için peutPasser adlı bir boole değeri kullanıyoruz. Senkronizasyonsuz bir kod yazımı şu şekilde olabilir:

while(! peutPasser);        // peutPasser'in true değerine geçmesini bekliyoruz
peutPasser=false;            // başka hiçbir iş parçacığı geçmemeli
section critique;            // burada iş parçacığı tek başına
peutPasser=true;            // başka bir iş parçacığı kritik bölüme girebilir

Bir iş parçacığının peutPasser ifadesinin doğru değerine geçmesini bekleyerek döngüye girdiği ilk komut, verimsizdir: iş parçacığı işlemciyi gereksiz yere meşgul eder. Buna aktif bekleme denir. Yazımı şu şekilde iyileştirebiliriz:

while(! peutPasser){        // peutPasser'in "true" değerine geçmesini bekliyoruz
   Thread.sleep(100);    // 100 ms boyunca duraklama
}
peutPasser=false;            // başka hiçbir iş parçacığı geçmemeli
section critique;            // burada iş parçacığı tek başına
peutPasser=true;            // başka bir iş parçacığı kritik bölüme girebilir

Buradaki bekleme döngüsü daha iyidir: iş parçacığı geçemezse, 100 ms boyunca uyku moduna geçer ve ardından geçip geçemeyeceğini tekrar kontrol eder. Bu sırada işlemci, sistemdeki diğer iş parçacıklarına tahsis edilir.

Aslında bu iki yöntem de hatalıdır: iki iş parçacığının aynı anda kritik bölüme girmesini engellemez. T1 iş parçacığının, peutPasser'in "true" olduğunu algıladığını varsayalım. Böylece bir sonraki komuta geçecek ve diğer iş parçacıklarını engellemek için peutPasser değerini "false" olarak ayarlayacaktır. Ancak bu anda, işlemci zaman dilimi dolduğu için, daha yüksek öncelikli bir görev işlemciyi talep ettiği için ya da başka bir nedenden dolayı kesintiye uğrayabilir. Sonuç olarak işlemciyi kaybeder. Biraz sonra işlemciyi tekrar ele geçirecektir. Bu arada, diğer görevler işlemciye erişecek; bunlardan biri de peutPasser'in "true" değerine geçmesini beklerken döngüye giren T2 iş parçacığı olabilir. O da peutPasser'in "true" olduğunu fark edecek (ilk iş parçacığı onu "false" olarak ayarlayacak zamanı bulamamıştır) ve o da kritik bölüme girecektir. Bu, olmaması gereken bir durumdur.

Sıra


while(! peutPasser){            // peutPasser'in "true" değerine geçmesini bekliyoruz
   try{
        Thread.sleep(100);    // 100 ms boyunca duraklama
    } catch (Exception e) {}
}// while
peutPasser=false;                // başka hiçbir iş parçacığı geçmemeli

senkronizasyonla korunması gereken kritik bir dizidir. Önceki örnekten esinlenerek şunu yazabiliriz:


    synchronized(synchro){
        while(! peutPasser){            // peutPasser'in true değerine geçmesini bekliyoruz
            try{
                Thread.sleep(100);    // 100 ms boyunca dur
            } catch (Exception e) {}
        }//while
        peutPasser=false;                // başka hiçbir iş parçacığı geçmemelidir
    }// senkronize
    section critique;                    // burada iş parçacığı tek başına
    peutPasser=true;                    // başka bir iş parçacığı kritik bölüme geçebilir

Bu örnek düzgün çalışıyor. İş parçacığının peutPasser boole değerini düzenli olarak izlerken yarı aktif bekleme durumuna girmesini önleyerek bu örneği iyileştirebiliriz. peutPasser'in durumunu kontrol etmek için her 100 ms'de bir düzenli olarak uyanmak yerine, iş parçacığı uyku moduna geçebilir ve peutPasser "true" olduğunda uyandırılmasını isteyebilir. Bunu şu şekilde yazabiliriz:


synchronized(synchro){
    if (! peutPasser) {
        try{
            synchro.wait();            // geçilemiyorsa bekleriz
        } catch (Exception e){
            
        }
    }
    peutPasser=false;            // başka hiçbir iş parçacığı geçmemelidir
}// senkronize

synchro.wait() işlemi, yalnızca synchro nesnesinin o anda "sahibi" olan bir iş parçacığı tarafından gerçekleştirilebilir. Burada sıra şöyledir:


synchronized(synchro){

}// senkronize

bu dizisi, iş parçacığının synchro nesnesinin sahibi olmasını sağlar. synchro.wait() işlemi ile iş parçacığı, senkronizasyon kilidinin sahipliğini devreder. Neden mi? Genellikle çalışmaya devam etmek için gerekli kaynaklara sahip olmadığı içindir. Bu nedenle, synchro kaynağını bekleyen diğer iş parçacıklarını engellemek yerine, kaynağı devreder ve kendisine eksik olan kaynağı beklemeye geçer. Örneğimizde, peutPasser boole değerinin true olmasına bekler. Bu olaydan nasıl haberdar olacak? Şu şekilde:


synchronized(synchro){
    if (! peutPasser) {
        try{
            synchro.wait();            // geçilemiyorsa bekleriz
        } catch (Exception e){
            
        }
    }
    peutPasser=false;            // başka hiçbir iş parçacığı geçmemeli
}// senkronize
section critique...
synchronized(synchro){
      synchro.notify();
    }

Senkronizasyon kilidini geçen ilk iş parçacığını ele alalım. Buna T1 diyelim. İlk iş parçacığı olduğu için peutPasser boole değerini "true" olarak bulduğunu varsayalım. Dolayısıyla bu değeri "false" olarak değiştirir. Ardından, synchro nesnesi tarafından kilitlenen kritik bölümden çıkar. Böylece başka bir iş parçacığı, peutPasser değerini test etmek için kritik bölüme girebilir. Bu iş parçacığı, değeri "yanlış" olarak bulur ve bir olay (wait) için beklemeye geçer. Bunu yaparken, synchro nesnesinin sahipliğini devreder. Böylece başka bir iş parçacığı kritik bölüme girebilir: o da peutPasser'in yanlış olması nedeniyle beklemeye geçer. Dolayısıyla, synchro nesnesi üzerinde bir olayı bekleyen birden fazla iş parçacığı olabilir.

Şimdi, sırası gelen T1 iş parçacığına geri dönelim. Bu iş parçacığı kritik bölümü yürütür ve ardından başka bir iş parçacığının artık geçebileceğini bildirir. Bunu şu sıra ile yapar:


synchronized(synchro){
      synchro.notify();
    }

Öncelikle synchronized komutuyla synchro nesnesini geri almalıdır. Bu bir sorun teşkil etmemelidir; zira bu iş parçacığı, synchro nesnesini geçici olarak ele geçiren ancak peutPasser nesnesini geçersiz buldukları için wait komutuyla onu bırakmak zorunda kalan diğer iş parçacıklarıyla rekabet halindedir. Dolayısıyla, T1 iş parçacığımız sonunda synchro nesnesinin sahipliğini elde edecektir. Bu işlem tamamlandıktan sonra, synchro.notify işlemiyle, synchro.wait tarafından bloke edilen iş parçacıklarından birinin uyandırılması gerektiğini belirtir. Ardından, synchro nesnesinin sahipliğini tekrar bırakır ve bu nesne bekleyen iş parçacıklarından birine verilir. Bu iş parçacığı, kendisini beklemeye alan wait komutunun ardından gelen komutla yürütmeye devam eder. Sıra kendisine geldiğinde, kritik bölümü yürütür ve başka bir iş parçacığını serbest bırakmak için bir synchro.notify komutu yürütür. Ve bu şekilde devam eder.

Daha önce incelediğimiz sayım örneği üzerinden bu çalışma şeklini inceleyelim.

  void btnGénérer_actionPerformed(ActionEvent e) {
     //iş parçacıkları oluşturma

     // oluşturulacak  parçacığı sayısı okunur
    int nbThreads=0;
    try{
      //  parçacığı sayısını içeren alan okunuyor
      nbThreads=Integer.parseInt(txtAGénérer.getText().trim());
      // pozitif >
      if(nbThreads<=0) throw new Exception();
    }catch(Exception ex){
       //hata
      txtStatus.setText("Nombre invalide");
       // yeniden başlıyor
      txtAGénérer.requestFocus();
      return;
    }//yakalama

     // RAZ görev sayacı
    txtGénérés.setText("0");  // görev sayacı 0'da
    // 1.  parçacığı geçebilir
    peutPasser=true;
     //  parçacıkları oluşturulur ve başlatılır
    tâches=new Thread[nbThreads];
    compteurs=new int[nbThreads];
    for(int i=0;i<tâches.length;i++){
      // i  parçacığı oluşturuluyor
      tâches[i]=new Thread() {
          public void run() {
          synchronise();
        }
      };//i  parçacığı
       // adı tanımlanıyor
      tâches[i].setName(""+i);
      // çalıştırılıyor
      tâches[i].start();
    }//for
  }//üretme

Artık iş parçacıkları incrémente yöntemini değil, aşağıdaki synchronise yöntemini çalıştırıyor:

   // iş parçacıklarının senkronizasyon aşaması
  public void synchronise(){
     // kritik bölüme erişim talebi
    synchronized(synchro){
      try{
        // geçilebilir mi?
        if(! peutPasser){
          System.out.println(Thread.currentThread().getName()+ " en attente");
          synchro.wait();
        }
         // geçildi - diğer iş parçacıklarının geçmesi engellendi
        peutPasser=false;
      } catch(Exception e){
        txtStatus.setText(""+e);
                    return;
      }//try
    }// synchronized

    // kritik bölüm
    System.out.println(Thread.currentThread().getName()+ " passé");
    incrémente();

     // işimiz bitti - kritik bölümün girişinde bloke olmuş olabilecek bir iş parçacığını serbest bırakıyoruz
    peutPasser=true;
    System.out.println(Thread.currentThread().getName()+ " terminé");
    synchronized(synchro){
      synchro.notify();
    }// senkronize
  } // senkronize

synchronise yöntemi, iş parçacıklarını tek tek geçirmeyi amaçlamaktadır. Bunun için synchro senkronizasyon değişkenini kullanır. incrémente yöntemi artık synchronized anahtar sözcüğüyle korunmamaktadır:

   // artırma
  private void incrémente(){
     // iş parçacığı numarasını alıyoruz
    int iThread=0;
    try{
      iThread=Integer.parseInt(Thread.currentThread().getName());
    }catch(Exception ex){}
     // iş sayacının değeri okunuyor
    try{
      compteurs[iThread]=Integer.parseInt(txtGénérés.getText());
    } catch (Exception e){}
     // artırılıyor
    compteurs[iThread]++;
     // 100 milisaniye beklenir - iş parçacığı bu sırada işlemciyi kaybeder
    try{
      Thread.sleep(100);
    } catch (Exception e){
      System.exit(0);
    }
     // yeni sayacı görüntülenir
    txtGénérés.setText("");
    txtGénérés.setText(""+compteurs[iThread]);
  }// artırılır

5 iş parçacığı için elde edilen sonuçlar şunlardır:

0 passé
1 en attente
2 en attente
3 en attente
4 en attente
0 terminé
1 passé
1 terminé
2 passé
2 terminé
3 passé
3 terminé
4 passé
4 terminé

Uygulama tarafından oluşturulan 5 iş parçacığı T0 ile T4 arasında olsun. T0, synchro kilidinin sahipliğini ilk olarak alır ve peutPasser'in doğru olduğunu tespit eder. peutPasser'i "false" olarak ayarlar ve devam eder: ilk mesaj olan passé'in anlamı budur. Büyük olasılıkla, devam eder ve kritik bölümü, özellikle de incrémente yöntemini yürütür. Bu yöntemde, 100 ms süreyle beklemeye geçer (sleep). Böylece işlemciyi serbest bırakır. İşlemci, T1 adlı başka bir iş parçacığına tahsis edilir ve bu iş parçacığı, synchro nesnesinin sahipliğini alır. Geçemeyeceğini fark eder ve bekleme durumuna geçer (wait). Ardından, synchro nesnesinin sahipliğini ve işlemciyi serbest bırakır. İşlemci, T2 iş parçacığına tahsis edilir ve bu iş parçacığı da aynı kaderi paylaşır. T0'in 100 ms süren durma süresi boyunca, T1 ile T4 arasındaki iş parçacıkları beklemeye alınır. İşte bu, 4 adet "beklemede" mesajının anlamıdır. 100 ms sonra, T0 işlemciyi geri alır ve işini tamamlar: “0 terminé” mesajının anlamı budur. Ardından, bloke edilmiş iş parçacıklarından birini serbest bırakır ve sonlanır. Serbest kalan işlemci, o anda kullanılabilir durumda olan bir iş parçacığına atanır: az önce serbest bırakılan iş parçacığına. Burada bu, T1'tir. T1 iş parçacığı daha sonra kritik bölüme girer: "1 passé" mesajının anlamı budur. Yapması gerekeni yapar ve sırayla 100 ms süreyle durur. İşlemci bu noktada başka bir iş parçacığı için kullanılabilir hale gelir, ancak tüm iş parçacıkları bir olayı beklemektedir: hiçbiri işlemciyi devralamaz. 100 ms sonra, T1 iş parçacığı işlemciyi geri alır ve sona erer: "1 terminé" mesajının anlamı budur. T1 ile T4 arasındaki iş parçacıkları, T1 ile aynı davranışı gösterecektir: "geçti", "tamamlandı" şeklindeki üç mesaj dizisinin anlamı budur.