Java. Synchronization. Monitor. General concepts. The synchronized keyword




Synchronization. Monitor. General concepts. The synchronized keyword


Contents


Search other resources:

1. The concept of synchronization between threads. The need to apply synchronization. Monitor

There are times when two or more parallel-executing threads are trying to access a shared resource. If the resource can be changed as a result of the execution of one of the threads, then the other threads must wait until the changes in the thread are completed. Otherwise, threads will receive a resource whose data will be erroneous.

Synchronization can ensure that only one thread uses a shared resource at a time. This means that synchronization is a process that orders access from different threads to a shared resource. Monitor is an object that is used for mutually exclusive locking.

Mutual locking allows only one thread object to own the monitor. Each thread object has its own, implicitly associated monitor.

The thread of execution (which is represented by the object) can take over the monitor if it has requested a lock and the monitor is free at the moment. After an object has entered the monitor, all other thread objects attempting to enter the monitor are suspended and wait until the first object exits the monitor. Only one thread can have a monitor. If the stream (object) has a monitor, then it can re-enter it if necessary.

In the Java language, synchronization is applied to entire methods or pieces of code.

Based on this, there are two ways to synchronize the program code:

  • by using the synchronized access modifier;
  • by using the synchronized () {} operator.

 

2. The synchronized access modifier. General form

The synchronized access modifier is applied when a synchronized method is declared and has the following general form:

synchronized return_type MethodName(parameters) {
  ...
}

here

  • MethodName is the name of the method that is a shared resource and needs to be synchronized;
  • return_type – the type that the method returns;
  • parameters – method parameters.

 

3. The synchronized() { } operator. General form

The synchronized() operator, unlike the synchronized access modifier, is used to apply synchronization to objects (methods) that were not originally designed for multithreading access. That is, the class does not implement methods with the synchronized access modifier (third-party classes).

The general form of the synchronized () statement is as follows:

synchronized (reference) {
  // Operators to be synchronized
  // ...
}

here

  • reference – a reference to the object being synchronized;
  • curly braces {} that define a sync block. This block specifies the operators for synchronization.

 

4. An example demonstrating synchronized access to a shared method from three different threads. Applying the synchronized access modifier

The example demonstrates the need to use the synchronized access modifier in order to order resource access from different threads.

The shared resource is the Get() method of the Array5 class. The Get() method returns an array of 5 numbers, whose values change sequentially from 1 to 5. This method will be attempted to be accessed simultaneously from different threads, so the method is designated as synchronized.

The encapsulation of a single thread of execution is carried out in the ArrayThread class in the classical (standard) way using the implementation of the Runnable interface. The ArrayThread class contains the following public members:

  • A is a reference to the instance of the Array5 class. This reference is used to access the Get() method;
  • AI – an array of numbers obtained from the Get() method;
  • t – a reference to the current thread;
  • constructor ArrayThread(), which receives an instance of the Array5 class and the name of the thread. In the constructor, a thread of execution is created and started;
  • the run() method, which calls the Get() method using reference A.

The Threads class provides a main() function that creates three threads AT1, AT2, AT3 of type ArrayThread and one object A5.

 

// Topic. Synchronization of streams.
// The class containing the shared resource
class Array5 {
  // A synchronized method that returns an array of 5 numbers
  // and prints it to the screen
  synchronized public int[] Get(String threadName) {
    // Print the name of the running thread
    System.out.println("Begin => " + threadName);

    // Form 5 integers from 1 to 5
    int[] AI = new int[5];
    for (int i=0; i<AI.length; i++)
      AI[i] = i+1;

    // Cause a delay, so that other threads can be executed
    try {
      Thread.sleep(100);
    }
    catch (InterruptedException e) {
      System.out.println(e.getMessage());
    }

    // Output the AI array in the method for control
    System.out.print(threadName + " = [ ");
    for (int i=0; i<AI.length; i++)
      System.out.print(AI[i] + " ");
    System.out.println(" ]");

    // Notify about the end of method execution
    System.out.println("End => " + threadName);
    return AI;
  }
}

// A class that is a thread that reads an array of numbers
class ArrayThread implements Runnable {
  Array5 A; // the instance of a shared resource
  int[] AI; // an array of 5 numbers that is obtained from a shared resource
  Thread t; // a reference to the current thread

  // Constructor - receives parameters:
  // - stream name;
  // - an instance of the Array5 class.
  ArrayThread(String threadName, Array5 A) {
    // Save array
    this.A = A;

    // Create a thread
    t = new Thread(this, threadName);

    // Start the thread for execution
    t.start(); // invoke the run() method
  }

  // Method in which a thread is started that creates an array of 5 numbers
  public void run() {
    AI = A.Get(t.getName()); // get an array of numbers from 1 to 5
  }
}

public class Threads {

  public static void main(String[] args) {
    // Demonstrate access to a shared resource from 3 different threads
    // 1. Declare an instance to be a shared resource on three different threads
    Array5 A5 = new Array5();

    // 2. Declare three different threads and run them
    ArrayThread AT1 = new ArrayThread("AT1", A5);
    ArrayThread AT2 = new ArrayThread("AT2", A5);
    ArrayThread AT3 = new ArrayThread("AT3", A5);

    // 3. Wait for all three threads to complete
    try {
      AT1.t.join();
      AT2.t.join();
      AT3.t.join();
    }
    catch (InterruptedException e) {
      System.out.println(e.getMessage());
    }
  }
}

The result of the program

Begin => AT1
AT1 = [ 1 2 3 4 5 ]
End => AT1
Begin => AT3
AT3 = [ 1 2 3 4 5 ]
End => AT3
Begin => AT2
AT2 = [ 1 2 3 4 5 ]
End => AT2

If in the above example, before the Get() method of the Array5 class, remove the synchronized keyword

class Array5 {

  ...

  // there is no synchronized modifier here
  public int[] Get(String threadName) {
    ...
  }
}

then there will be no sequential execution of threads. In this case, after each launch, the program will produce a different (chaotic) result, for example, the following

Begin => AT1
Begin => AT2
Begin => AT3
AT1 = [ AT2 = [ 1 1 2 2 3 3 4 4 5 ]
End => AT1
5 ]
End => AT2
AT3 = [ 1 2 3 4 5 ]
End => AT3

 

5. An example of using the synchronized () {} operator for synchronized access to a shared resource

The code of the previous example (see section 4), synchronizing threads, can be represented in a different way. In this case, instead of the synchronized access modifier, use the synchronized() {} statement before the Get() method name. The synchronized statement must be used in the run() method of the ArrayThread class. After the changes made, the abbreviated program code will be as follows:

// A class that is a shared resource
class Array5 {
  // A method that returns an array of 5 numbers and displays it on the screen.
  // It is not synchronized already
  public int[] Get(String threadName) {
    ...
  }
}

// A class that is a thread reading an array of numbers
class ArrayThread implements Runnable {
  Array5 A; // the instance of shared resource
  int[] AI; // an array of 5 numbers from a shared resource
  Thread t; // a reference to the current thread

  // Constructor - receives parameters:
  // - thread name;
  // - the instance of Array5 class.
  ArrayThread(String threadName, Array5 A) {
    ...
  }

  // Method in which a thread is started that creates an array of 5 numbers
  public void run() {
    // Operator synchronized to the shared resource A
    synchronized (A) {
      AI = A.Get(t.getName()); // get an array of numbers from 1 to 5
    }
  }
}

 


Related topics