Introduction to Java Multithreading - Sharing Access to Data






Synchronization for Controlled Access


Java provides two keywords for ensuring that data can be shared between threads in a controlled manner: synchronized and volatile.

Synchronized has two important definitions: it ensures that only one thread executes a protected section of code at one time (mutual exclusion or mutex), and it ensures that data changed by one thread is visible to other threads (visibility of changes).

Without synchronization, it is easy for data to be left in an inconsistent state. For example, if one thread is updating two related values (say, the position and velocity of a ball), and another thread is reading those two values, it is possible that the second thread could be scheduled to run after the first thread has written one value but not the other, thus seeing one old and one new value.

Volatile Keyword

Volatile is simpler than synchronization and is suitable only for controlling access to single instances of primitive variables -- integers, doubles, booleans and so on. When a variable is declared volatile, any write to that variable will go directly to main memory, bypassing the cache, while any read of that variable will come directly from main memory, bypassing the cache. This means that all threads see the same value for a volatile variable at all times.

Without proper synchronization, it is possible for threads to see old values of variables or experience other forms of data corruption.

Volatile is useful for ensuring that each thread sees the most recent value for a variable, but sometimes we need to protect access to larger portions of code. Synchronization uses the concepts of monitors, or locks, to coordinate access to particular blocks of code.

Every Java object has an associated lock. Java locks can be held by no more than one thread at a time. When a thread enters a synchronized block of code, the thread blocks and waits until the lock is available, acquires the lock when it becomes available, and then executes the block of code. It releases the lock when control exits the protected block of code, either by reaching the end of the block or when an exception is thrown that is not caught within the synchronized block.


Synchronized Methods

The simplest way to create a synchronized block is to declare a method as synchronized. This means that before entering the method body, the caller must acquire a lock:

public class Point {
 public synchronized void setXY(int x, int y) {
  this.x = x;
  this.y = y;
 }
}


Synchronized Blocks

The syntax for synchronized blocks is a bit more complicated than for synchronized methods because you also need to explicitly specify what lock is being protected by the block. The following version of Point is equivalent to the version shown in the previous:

public class Point {
 public void setXY(int x, int y) {
  synchronized (this) {
   this.x = x;
   this.y = y;
  }
 }
}


A Simple Synchronization Example

In the following example, the two threads are free to execute the synchronized block in setLastAccess() simultaneously because each thread has a different value for inner. Therefore, the synchronized block is protected by different locks in the two executing threads.

import java.util.Date;

public class SyncExample {
 public static class Inner {
  private Date date;

  public synchronized void setLastAccess(Date date) {
   this.date = date;
   System.out.println(date);
  }
  
 }

 public static class MyThread implements Runnable {
  private Inner inner;

  public MyThread(Inner inner) {
   this.inner = inner;
  }

  public void run() {
   inner.setLastAccess(new Date());
  }
 }

 public static void main(String[] args) {
  MyThread m1 = new MyThread(new Inner());
  MyThread m2 = new MyThread(new Inner());

  Thread t1 = new Thread(m1);
  Thread t2 = new Thread(m2);

  t1.start();
  t2.start();
 }
}

Alright, that's all for now. Hope you enjoyed these tutorials. Until next time, happy coding!
Author:

Software Developer, Codemio Admin


Disqus Comments Loading..