Concurrent Programming - Threads: Diferență între versiuni

De la WikiLabs
Jump to navigationJump to search
(Pagină nouă: Java language natively supports ([http://en.wikipedia.org/wiki/Thread_(computing) threads]), which are several sequences of instructions running in parallel, but belonging to the same...)
 
 
(Nu s-au afișat 2 versiuni intermediare efectuate de același utilizator)
Linia 1: Linia 1:
Java language natively supports ([http://en.wikipedia.org/wiki/Thread_(computing) 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:
Java language natively supports [http://en.wikipedia.org/wiki/Thread_(computing) 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:
* by extending class [http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html java.lang.Thread];
* by extending class [http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html java.lang.Thread];
* by implementing interface [http://docs.oracle.com/javase/7/docs/api/java/lang/Runnable.html java.lang.Runnable].
* by implementing interface [http://docs.oracle.com/javase/7/docs/api/java/lang/Runnable.html java.lang.Runnable].
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{
Linia 182: Linia 183:
</syntaxhighlight>
</syntaxhighlight>


== ''Race conditions'' - bariera ==
== Race Conditions - Barriers ==
 
In the example above, is there are two threads, one writing elements onto the stack and one consuming them, we can see that if any of the threads is slightly faster, it will end up throwing an exception, either because the stack gets empty or full. This is what's called a race condition: one more code sections, executed by two or more threads that are expected to be executed in a particular order that ends up being inverted. So in the example, if the stack is empty, then the thread pushing should take the monitor before the one popping, or an exception will be thrown.


Î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.  
This problem is solved by using a barrier system, which is a system that stops a thread in a certain spot, until a condition is met. In Java, this is done by calling methods ''wait()'' / ''wait(long)'' / ''wait(long, int)'' and ''notify()'' / ''notifyAll()'' belonging to class Object.


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()''.
Methods ''wait()'' / ''wait(long)'' / ''wait(long, int)'' block the current thread until another thread calls one of the ''notify()'' / ''notifyAll()'' methods for the same object, until the time given as an argument has expired or until another thread interrupts the waiting thread by calling ''Thread.interrupt()''.


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.  
<div class="regula"><font color="#ff0000">Rule:</font>Calls to ''wait()'' methods are always done in a synchronized section and are called for the monitor object.</div>


<div class="regula"><font color="#ff0000">Regulă:</font> Apelurile metodelor de tip ''wait()'' se fac obligatoriu într-un bloc ''synchronized'' și se folosește ca referință obiectul monitor:</div>


<syntaxhighlight lang="Java">
<syntaxhighlight lang="Java">
Linia 249: Linia 251:
</syntaxhighlight>
</syntaxhighlight>


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()''.
Methods of type ''notify()'' reactivate waiting threads. These don't have to be called from within a synchronized section, but it is recommended to do so. They are called for the monitor objects used to call wait on the waiting threads.


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ă.
A thread reactivated using ''notify'' needs to wait for the monitor unlocking in order to enter the synchronized section, like any other thread. Notice this behavior in method ''pop()'', where ''notifyAll()'' has been called, before extracting the element from the stack, on the previous line. The program functions correctly because no other thread will start execution until the current thread exists the synchronized area.


Resurse externe:
External resources:
# http://docs.oracle.com/javase/tutorial/essential/concurrency/
# http://docs.oracle.com/javase/tutorial/essential/concurrency/
# http://www.tutorialspoint.com/java/java_multithreading.htm
# http://www.tutorialspoint.com/java/java_multithreading.htm

Versiunea curentă din 23 decembrie 2013 13:45

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 - Barriers

In the example above, is there are two threads, one writing elements onto the stack and one consuming them, we can see that if any of the threads is slightly faster, it will end up throwing an exception, either because the stack gets empty or full. This is what's called a race condition: one more code sections, executed by two or more threads that are expected to be executed in a particular order that ends up being inverted. So in the example, if the stack is empty, then the thread pushing should take the monitor before the one popping, or an exception will be thrown.

This problem is solved by using a barrier system, which is a system that stops a thread in a certain spot, until a condition is met. In Java, this is done by calling methods wait() / wait(long) / wait(long, int) and notify() / notifyAll() belonging to class Object.

Methods wait() / wait(long) / wait(long, int) block the current thread until another thread calls one of the notify() / notifyAll() methods for the same object, until the time given as an argument has expired or until another thread interrupts the waiting thread by calling Thread.interrupt().

Rule:Calls to wait() methods are always done in a synchronized section and are called for the monitor object.


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();
    }
}

}

Methods of type notify() reactivate waiting threads. These don't have to be called from within a synchronized section, but it is recommended to do so. They are called for the monitor objects used to call wait on the waiting threads.

A thread reactivated using notify needs to wait for the monitor unlocking in order to enter the synchronized section, like any other thread. Notice this behavior in method pop(), where notifyAll() has been called, before extracting the element from the stack, on the previous line. The program functions correctly because no other thread will start execution until the current thread exists the synchronized area.

External resources:

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