Diferență între revizuiri ale paginii „Concurrent Programming - Threads”

De la WikiLabs
Jump to navigationJump to search
Linia 102: Linia 102:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
== Synchronizing Threads - Semaphore ==
+
== Synchronizing Threads - Semaphores ==
  
Există situații, în aplicații multithread-ed, în care mai multe thread-uri accesează aceeași resursă. În unele din aceste situații, dacă unele metode sau zone de program ale acestor resurse comune sunt accesate de mai multe thread-uri în același timp, pot apărea condiții limită care duc la un comportament incorect al aplicației. Aceste zone trebuie să fie executate ''atomic'', adică în timpul execuției lor, nici un alt thread nu trebuie să întrerupă thread-ul curent sau să execute aceeași bucată de cod.
+
There are situations in multithreading applications, where multiple threads are accessing a common resource. In some of these situations, if some methods are program areas using these common resources are accessed by multiple threads at the same time, certain events occur that result in the malfunction of the application. These areas need to be executed atomically, meaning that during their execution by a thread, no other thread can interrupt the current thread or execute the same area.
  
În mașina virtuală, acest lucru se realizează cu ajutorul unui sistem de monitoare. Monitorul este un câmp ascuns definit în clasa Object (astfel încât orice obiect poate fi folosit pe post de monitor) care contorizează thread-urile care accesează una sau mai multe bucăți de program. Aceste bucăți de program se numesc ''sincronizate''. Dacă un thread urmează să intre într-o zonă sincronizată, atunci mașina virtuală verifică dacă monitorul e liber, adică dacă nici un alt thread nu execută instrucțiuni din vreo zonă sincronizată de acel monitor. Dacă monitorul e ocupat (adică dacă alt thread îl deține), arunci thread-ul curent se oprește, așteptând eliberarea lui. Dacă este liber, atunci thread-ul curent îl ocupă, începând execuția codului.
+
In the virtual machine, this is achieved with a system of monitors. A monitor is a hidden field defined in class Object (so that by inheriting it, any object can be used as a monitor) which keeps track of the threads that access one or more code sections. These sections are called ''synchronized''. If a thread is about to enter a synchronized section, then the virtual machine verifies if the monitor is not locked, meaning that no other thread executes instructions in any section synchronized by that monitor. The the monitor is locked (if another thread has it locked), then the current thread will stop and wait for its release. If the monitor is unlocked, the thread will lock it and it will continue execution.
  
În Java, cuvântul cheie pentru sincronizarea unei bucăți de cod este ''synchronized''.
+
In Java, the keyword for synchronizing a code section is ''synchronized''.
  
 
<syntaxhighlight lang="Java">
 
<syntaxhighlight lang="Java">
Linia 159: Linia 159:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
În exemplul de mai sus, dacă două thread-uri apelează în același timp metodele ''push(Object)'' și ''pop()'', atunci, dacă acestea nu ar fi sincronizate, ar putea apărea situații în care contorul ''stackTop'' ar putea fi incrementat de un thread, apoi decrementat de al doilea înainte ca primul să efectueze scrierea în vectorul ''stack''. Dar folosind cuvântul cheie ''syncronized'', ne-am asigurat că nici una din metodele declarate astfel și nici una din bucățile de program sincronizate nu vor fi executate în același timp de thread-uri diferite.
+
In the example above, if two threads call methods ''push(Object)'' and ''pop()'' at the same time and they would not be synchronized, situations might occur when the counter ''stackTop'' would be incremented by one thread, then decremented by the second before the first finishes writing in the array ''stack'', effectively writing in the wrong position. By using keyword ''synchronized'', we are safe to assume that none of the methods declared as such and none of the synchronized sections will be executed at the same time by different threads.
  
<div class="regula"><font color="#ff0000">Regulă:</font> O clasă nu poate executa o porțiune de program sincronizată al cărei monitor aparține altui thread DAR poate executa o metodă sincronizată de un monitor care îi aparține. Ca exemplu, zona sincronizată din metoda ''push(Object)'' apelează metoda ''full()'' care este la rândul ei sincronizată.</div>
+
<div class="regula"><font color="#ff0000">Rule:</font>A thread can not execute a synchronized section belonging to another thread BUT it can execute a synchronized section that already belongs to it. As an example, the synchronized section in method ''push(Object)'' is calling method ''full()'', which is itself synchronized by the same monitor.</div>
  
Dacă o metodă este declarată ''synchronized'', acest lucru este echivalent cu a declara tot conținutul metodei într-un bloc ''synchronized(this)''. Altfel spus, o metodă declarată ''synchronized'' este automat sincronizată folosind ca monitor obiectul curent.
+
If a method is declared synchronized, this is equivalent of declaring the whole method content in a ''synchronized(this)'' block. Therefore, a method declared ''synchronized'' is automatically synchronized using the current object as a monitor.
 +
 
 +
If a static method is declared ''synchronized'', this is equivalent as declaring the whole method content in a ''synchronized(<class_name>.class)'' block. Therefore, a static method declared ''synchronized'' is automatically synchronized using the object of type Class associated with the current class as a monitor.
  
Dacă o metodă statică este declarată ''synchronized'', acest lucru este echivalent cu a declara tot conținutul metodei într-un bloc ''synchronized(<nume_clasa>.class)''. Altfel spus, o metodă declarată ''synchronized'' este automat sincronizată folosind ca monitor obiectul de tip '''Class''' asociat clasei:
 
 
<syntaxhighlight lang="Java">
 
<syntaxhighlight lang="Java">
 
public class StaticSync{
 
public class StaticSync{

Versiunea de la data 23 decembrie 2013 13:14

Java language natively supports threads, which are several sequences of instructions running in parallel, but belonging to the same application. An example would be a server which accepts and manages several connections at the same time. There are two ways of implementing a thread:

Class Thread as well as interface Runnable define a method called run(). This method is the start point of the new thread, just like public static void main(String[]) for the main thread. This method run() can be called in two ways:

  • directly calling it, in which case it is ran on the same thread, as normal method;
  • calling method start(), which starts a new thread and starts executing method run() in parallel.
public class PrintThread extends Thread{

    private int index;

public PrintThread(int _index){
    index = _index;
}

public void run(){
    for(int i=0; i<5; i++){
        System.out.println("This is thread " + index);
        try{
            //pause for 0.5 seconds (500 ms)
            Thread.sleep(500);
        }catch(InterruptedException _ie){
            System.out.println(_ie.getMessage());
        }
    }
}

}

This is the normal call of method run(). The program will first display the test on thread 1 five times, then the one on thread 2 five times, then the one on thread 3, etc:

public class NormalStarter{

public static void main(String[] _args){
    for(int i=0; i<5; i++){
        PrintThread _thread = new PrintThread(i + 1);
        _thread.run();
    }
}

}

This is the behavior when calling start(). The program will display texts from all threads at the same time:

public class ThreadStarter{

public static void main(String[] _args){
    for(int i=0; i<5; i++){
        PrintThread _thread = new PrintThread(i + 1);
        _thread.start();
    }
}

}

Same thing by using the Runnable interface:

public class PrintRunnable implements Runnable{

    private int index;

public PrintRunnable(int _index){
    index = _index;
}

public void run(){
    for(int i=0; i<10; i++){
        System.out.println("This is thread " + index);
        try{
            //pause for 1 second (1000 ms)
            Thread.sleep(1000);
        }catch(InterruptedException _ie){
            System.out.println(_ie.getMessage());
        }
    }
}

}


public class ThreadStarterRunnable{

public static void main(String[] _args){
    for(int i=0; i<5; i++){
        // polymorphism: PrintRunnable is a Runnable
        Runnable _runnable = new PrintRunnable(i + 1);

        PrintThread _thread = new Thread(_runnable);
        _thread.start();
    }
}

}

Synchronizing Threads - Semaphores

There are situations in multithreading applications, where multiple threads are accessing a common resource. In some of these situations, if some methods are program areas using these common resources are accessed by multiple threads at the same time, certain events occur that result in the malfunction of the application. These areas need to be executed atomically, meaning that during their execution by a thread, no other thread can interrupt the current thread or execute the same area.

In the virtual machine, this is achieved with a system of monitors. A monitor is a hidden field defined in class Object (so that by inheriting it, any object can be used as a monitor) which keeps track of the threads that access one or more code sections. These sections are called synchronized. If a thread is about to enter a synchronized section, then the virtual machine verifies if the monitor is not locked, meaning that no other thread executes instructions in any section synchronized by that monitor. The the monitor is locked (if another thread has it locked), then the current thread will stop and wait for its release. If the monitor is unlocked, the thread will lock it and it will continue execution.

In Java, the keyword for synchronizing a code section is synchronized.

public class Stack{

    public static final int MAX_STACK_SIZE = 128;
    
    private Object[] stack;
    private int stackTop;

public Stack(){
    this(MAX_STACK_SIZE);
}

public Stack(int _maxSize){
    stack = new Object[_maxSize];
    stackTop = 0;
}

public synchronized boolean empty(){
    return stackTop == 0;
}

public synchronized boolean full(){
    return stackTop == stack.length;
}


public Object pop() throws Exception{
    synchronized(this){
        if(!empty()){
            return stack[--stackTop];
        }
    }

    throw new Exception("Stack empty");
}

public void push(Object _obj) throws Exception{
    synchronized(this){
        if(!full()){
            stack[stackTop++] = _obj;
        }
    }

    throw new Exception("Stack full");
}

}

In the example above, if two threads call methods push(Object) and pop() at the same time and they would not be synchronized, situations might occur when the counter stackTop would be incremented by one thread, then decremented by the second before the first finishes writing in the array stack, effectively writing in the wrong position. By using keyword synchronized, we are safe to assume that none of the methods declared as such and none of the synchronized sections will be executed at the same time by different threads.

Rule:A thread can not execute a synchronized section belonging to another thread BUT it can execute a synchronized section that already belongs to it. As an example, the synchronized section in method push(Object) is calling method full(), which is itself synchronized by the same monitor.

If a method is declared synchronized, this is equivalent of declaring the whole method content in a synchronized(this) block. Therefore, a method declared synchronized is automatically synchronized using the current object as a monitor.

If a static method is declared synchronized, this is equivalent as declaring the whole method content in a synchronized(<class_name>.class) block. Therefore, a static method declared synchronized is automatically synchronized using the object of type Class associated with the current class as a monitor.

public class StaticSync{

public static synchronized void printSmth(){
    System.out.println("Smth");
}

public static void printSmthElse(){
    synchronized(StaticSync.class){
        System.out.println("SmthElse");
    }
}

}

Race conditions - bariera

În exemplul anterior, dacă considerăm două thread-uri, unul care pune elemente pe stivă și unul care le consumă, observăm că dacă oricare din thread-uri este mai rapid decât celălalt, se ajunge în situația în care se aruncă o excepție, ori pentru că stiva e plină, ori pentru că s-a golit. Aici apare ceea ce se numește race condition, adică există una sau două zone de program executate de două thread-uri diferite și în care unul din thread-uri trebuie să ajungă înaintea celuilalt ca programul să se desfășoare corect. În exemplul anterior, dacă stiva este goală, atunci thread-ul care pune un element pe stivă trebuie să ajungă la metoda push(Object) înainte ca celălalt thread să apeleze metoda pop(), în caz contrar generându-se o excepție.

Această problemă se rezolvă folosind un sistem de bariere, adică un sistem care oprește unul din thread-uri într-un anumit loc până când o condișie este îndeplinită (de cele mai multe ori, când alt thread ajunge în locul potrivit). În Java, acest lucru se realizează cu ajutorul metodelor wait()/ wait(long)/ wait(long, int) și notify()/ notifyAll().

Metodele wait()/ wait(long)/ wait(long, int) blochează thread-ul curent până când un alt thread apelează una din metodele notify()/ notifyAll(), sau până când timpul dat ca argument a expirat.

Regulă: Apelurile metodelor de tip wait() se fac obligatoriu într-un bloc synchronized și se folosește ca referință obiectul monitor:
public class Stack{

    public static final int MAX_STACK_SIZE = 128;
    
    private Object[] stack;
    private int stackTop;

public Stack(){
    this(MAX_STACK_SIZE);
}

public Stack(int _maxSize){
    stack = new Object[_maxSize];
    stackTop = 0;
}

public synchronized boolean empty(){
    return stackTop == 0;
}

public synchronized boolean full(){
    return stackTop == stack.length;
}


public Object pop(){
    synchronized(this){
        while(empty()){
            try{
                this.wait();
            }catch(InterruptedException _ie){
                  System.out.println("InterruptedException: " + _ie.getMessage());
            }
        }
        this.notifyAll();
        return stack[--stackTop];
    }
}

public void push(Object _obj){
    synchronized(this){
        while(full()){
            try{
                this.wait();
            }catch(InterruptedException _ie){
                  System.out.println("InterruptedException: " + _ie.getMessage());
            }
        }
        stack[stackTop++] = _obj;
        this.notifyAll();
    }
}

}

Metodele de tip notify() reactivează threadurile oprite folosind metodele wait(). Acestea nu trebuie obligatoriu să fie apelate dintr-un bloc sincronizat, dar este recomandat. Se folosește ca referință tot obiectul de tip monitor utilizat pentru apelul metodelor wait().

Un thread reactivat folosind notify() trebuie să aștepte eliberarea monitorului pentru a executa zona sincronizată, ca orice alt thread. Observați utilizarea acestei funcționalități în metoda pop(), unde s-a apelat notifyAll() înainte de extragerea elementului de pe stivă, pe linia următoare. Totuși, clasa funcționează corect, pentru că thread-ul care introduce pe stivă un element nou nu va intra în execuție până când thread-ul care apelează notifyAll() nu iese din zona sincronizată, adică după extragerea unui element de pe stivă.

Resurse externe:

  1. http://docs.oracle.com/javase/tutorial/essential/concurrency/
  2. http://www.tutorialspoint.com/java/java_multithreading.htm