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:
şu anda yürütülmekte olan iş parçacığını verir | |
iş parçacığının adını belirler | |
iş parçacığının adı | |
iş parçacığının aktif olup olmadığını (true) veya olmadığını (false) belirtir | |
bir iş parçacığının yürütülmesini başlatır | |
önceki start yöntemi yürütüldükten sonra otomatik olarak yürütülen yöntem | |
bir iş parçacığının çalışmasını n milisaniye boyunca durdurur | |
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:
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. | |
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ş 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:
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.

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.

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:
![]() |
|
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:
İş 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:
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:
- txtHorloge metin kutusunda geçerli saati gösterir
- 1 saniye bekler
- ö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:

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:

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:

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:
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ı:
Şimdi bunu farklı bir şekilde tanımlıyoruz:
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:

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:
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 iş parçacığı sayısı okunur
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
// RAZ görev sayacı
txtGénérés.setText("0"); // görev sayacı 0'da
// 1. iş parçacığı geçebilir
peutPasser=true;
// iş 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 iş parçacığı oluşturuluyor
tâches[i]=new Thread() {
public void run() {
synchronise();
}
};//i 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.



