JavaData & JavaFundamentals of Thread Safety in Java

Fundamentals of Thread Safety in Java

Introduction

Concurrent programming is bounded by the norms of thread safety. Almost like the wheels of a car, concurrency derives its mobility through the correct use of threads. The meaning ‘correctness’ and ‘safety’ of a thread has a deeper connotation; relying more on the intuitive understanding of the programmer rather than definition and etymological understanding of the purist, imagine what things we should consider when multiple thread accesses shared data with every chance of its mutability. Here, we shall consider thread safety and concurrency at a same parlance.

Motivation and Need Behind Concurrency

Computation and IO intensive business applications spurred the need for processors to have more number crunching ability. Hardware manufacturers are racing for multiple cores, hyperthreading, and terabytes of data. Software should work together with hardware to utilize the efficiency of today’s computers. We cannot expect a single threaded application to perform better in a multi-threaded environment. If hardware is built for concurrency, how can software lag behind? Java’s built-in support for threads simplifies the development of concurrent applications by providing language and library support and a formal cross-platform memory model. Concurrent programming is undoubtedly esoteric and requires a good understanding of the intricacies of threading. The ballet of thread execution must conform to the specification without unwanted interaction among them either in the short run or in the long run.

Grounding in Thread Safety

Creating a thread is one thing, but creating a thread that is safe is vital for concurrency. Code that works fine in a single threaded environment does not necessarily mean that it will perform well in a multi-threaded environment. Unlike sequential programming, concurrent programming has many issues to consider under its hood. These issues are the main reason for making concurrent programming esoteric. A safe thread ensures that, when multiple threads operate on shared data, the resultant information should adhere to the specification without causing any problem.

Problems of Unsafe Thread

In the class below, there is no problem in accessing the count in a single threaded environment. Although the code seems harmless, it is an ideal example of an unsafe thread.

public class Counter {
   private static int count = 0;
   public static int incrementCount() {
      return count++;
   }
}

Here, the increment action performed in an shared integer variable count is susceptible to the lost update problem because the compact syntax is actually a discreet sequence of a threefold operation: read the value, add one to it, and write back the new value. Say, two threads—ThreadA and ThreadB—access the incrementCount() method. Initially, the count is 5; ThreadA reads the value and is on the verge of incrementing it. Before the operation could complete, with some unlucky timing, ThreadB also reads the value 5 and increments it to 6. ThreadA, in the meantime, also updated the value to 6 because it had also read the value 5. Ultimately, the value, which should be 7, remains 6. This is clearly not what was supposed to happen. The result is inaccurate due to the lack of synchronization between the threads.

Making a Thread Safer: Example 1

We can rewrite the previous code as follows:

public class Counter {
   private static int count = 0;
   public static synchronized int incrementCount() {
      return count++;
   }
}

Here, we have marked the method with the keyword synchronized. This ensures mutual exclusion: that only one thread can access the method at a time and other threads wait in line. The operation of incrementing now becomes atomic, even if multiple threads modify the variable concurrently. Another way to use the synchronized keyword is to mark only the critical section of the code, rather than making whole method synchronized.

Making a Thread Safer: Example 2

We can make a thread safe in another way, by using Java’s built-in support for concurrency API.

import java.util.concurrent.atomic.AtomicInteger;
public class Counter {
   private static AtomicInteger count = new AtomicInteger(0);

   public static int incrementCount() {
      return count.getAndIncrement();
   }
}

Java Concurrency API: An Example

To get a glimpse of Java’s built-in support for concurrency API, let’s try another example.

public class Task implements Runnable {
   private Date date;
   private String name;

   public Task(String name) {
      date = new Date();
      this.name = name;
   }

   @Override
   public void run() {
   System.out.printf("Thread: %s Task: %s created on %sn", Thread
      .currentThread().getName(), name, date);
   System.out.printf("Thread: %s Task: %s started on %sn", Thread
      .currentThread().getName(), name, new Date());
   try {
      Long timeout = ((long) Math.random() * 10);
      System.out.println(Thread.currentThread().getName()
         + " with Task name: " + name
         + " is asleep for " + timeout+ " msec.");
         TimeUnit.SECONDS.sleep(timeout);
   } catch (InterruptedException intex) {
      intex.printStackTrace();
   }
   System.out.printf("Thread: %s Task: %s finished on %sn", Thread
      .currentThread().getName(), name, new Date());
   }
}


public class TaskServer {
   private ThreadPoolExecutor threadPoolExecutor;

   public TaskServer() {
      threadPoolExecutor = (ThreadPoolExecutor) Executors
          .newCachedThreadPool();
   }
   public void execute(Task task){
      threadPoolExecutor.execute(task);
      System.out.println("Pool size of the Server: "
      +threadPoolExecutor.getPoolSize());
      System.out.println("Active Task count: "
      +threadPoolExecutor.getActiveCount());
      System.out.println("Task Completed count: "
      +threadPoolExecutor.getCompletedTaskCount());
   }

   public void close(){
      threadPoolExecutor.shutdown();
   }
}

public class AppMain {

   public static void main(String[] args) {
      TaskServer server = new TaskServer();
      for (int i = 0; i < 50; i++) {
         Task task = new Task("Task " + i);
         server.execute(task);
      }
      server.close();
   }
}

Here, the TaskServer class creates a ThreadPoolExecutor object to execute tasks. Even though ThreadPoolExecutor has its own constructor, it is recommended to use the Executors class to create the object due to its complexity. We have used the newThreadCachedThreadPool() method to create an Executor object. The reason is, a cached thread pool creates new threads whenever there is a need to create a new Task and also reuses existing ones when they become available after finishing their current task. Creating a thread is a time-intensive process; moreover, why create new ones when we can utilize an existing resource? This not only boosts efficiency but also reduces performance clogs. Once the executor is created, we can send our Runnable (or Callable) type objects to execute. Here in this program we did nothing in particular in the execute() method except printing some log messages of information about the current state of the executor.

Once the ThreadPoolExecutor is run, it is critical that we shut it down explicitly; otherwise, it will continue running even though there is no task remaining to execute. In such a case, when there are no more tasks to execute and ThreadPoolExecutor is still running, it will wait for new tasks, and keep on waiting. In this case, we shut it down explicitly even if another task is sent; it will reject that task and throw a RejectedExecutionException.

Conclusion

Stateless objects are inherently thread safe. Because of their transient nature, they do not influence the result of other threads. The problem arises from the stateful objects that remember things from their past influence. Thread safety can be an issue to these types of objects. Creating thread safe methods in trivial cases is easy, but in a more complicated scenario it needs a lot of work. The rule of thumb rule is the purer the object oriented design, the easier is to make it thread safe down the line.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories