{{announcement.body}}
{{announcement.title}}

Java Concurrency: The Basics

DZone 's Guide to

Java Concurrency: The Basics

The ExecutorService interface is the easiest and recommended way to create and manage async tasks. We must first obtain an instance of an ExecutorService by ...

· Java Zone ·
Free Resource

The Power of Multi-Threading in Java

This article describes the basics of Java Concurrency API, and how to assign work to be done asynchronously.

A thread is the smallest unit of execution that can be scheduled by the operating system, while a process is a group of associated threads that execute in the same, shared environment.

You may also like: [DZone Refcard] Core Java Concurrency

We call “system thread” the thread created by the JVM that runs in the background of the application (e.g. garbage-collector), and a “user-defined thread” those which are created by the developer.

First, we need to know that in a standard Java program, all the work we implement is done in the main thread, so the work is done sequentially.

To change that behavior, we can create our own threads and program them to do the work “at the same time”, creating a multi-threaded environment.

We can create threads extending the Thread class or implement the Runnable interface. Both use the method run() to execute asynchronous work. A thread must be started by its start() method; otherwise, the work is not forked in a different thread.

Extending Thread Class

By extending the Thread class, we can override the run() method.

Given the two classes “Ping” and “Pong” below, they simulate three-time processing job that takes from 0 to 4 seconds to accomplish each one:

Java




x
22


 
1
class Ping extends Thread {
2
   @Override
3
   public void run() {
4
      for (int i = 0; i < 3; i++) {
5
         // simulates work that takes between from 0 to 4 sec
6
         try { Thread.sleep(new Random().nextInt(4000)); } 
7
         catch (InterruptedException e) { } 
8
         System.out.println("ping");
9
      }
10
   }
11
}
12
class Pong extends Thread {
13
   @Override
14
   public void run() {
15
      for (int i = 0; i < 3; i++) {
16
         // simulates work that takes between from 0 to 4 sec
17
         try { Thread.sleep(new Random().nextInt(4000)); } 
18
         catch (InterruptedException e) { } 
19
         System.out.println("pong");
20
      }
21
   }
22
}



If we instantiate them and call each method run(), the work will be done sequentially in the main thread, which is not our intention here:

Java




xxxxxxxxxx
1
10


 
1
public class MainClass {
2
   public static void main(String[] args) {
3
      System.out.println("Start of main thread");
4
      Thread ping = new Ping();
5
      Thread pong = new Pong();
6
      ping.run();   // wrong
7
      pong.run();   // wrong
8
      System.out.println("End of main thread");
9
   }
10
}



The output will be:

Java




xxxxxxxxxx
1


1
Start of main thread
2
ping
3
ping
4
ping
5
pong
6
pong
7
pong
8
End of main thread



But if we start the threads calling its start method, the work will be done asynchronously and there is no guarantee of the execution order.

Java




xxxxxxxxxx
1
10


 
1
public class MainClass {
2
   public static void main(String[] args) {
3
      System.out.println("Start of main thread");
4
      Thread ping = new Ping();
5
      Thread pong = new Pong();
6
      ping.start();   // async
7
      pong.start();   // async
8
      System.out.println("End of main thread");
9
   }
10
}



The output may be:

Java




xxxxxxxxxx
1


1
Start of main thread
2
End of main thread
3
pong
4
ping
5
ping
6
pong
7
pong
8
ping



Implementing Runnable Interface

Another way to create a thread is to implement the Runnable interface, which can be an advantage if your worker class already extends another class. Since there is no multiple-inheritance in Java, it would be impossible to extend to the Thread class.

The only modifications in the previous code is the implements keyword in worker classes:

Java




xxxxxxxxxx
1
22


 
1
class Ping implements Runnable {
2
   @Override
3
   public void run() {
4
      for (int i = 0; i < 3; i++) {
5
         // simulates work that takes between from 0 to 4 sec
6
         try { Thread.sleep(new Random().nextInt(4000)); } 
7
         catch (InterruptedException e) { } 
8
         System.out.println("ping");
9
      }
10
   }
11
}
12
class Pong implements Runnable {
13
   @Override
14
   public void run() {
15
      for (int i = 0; i < 3; i++) {
16
         // simulates work that takes between from 0 to 4 sec
17
         try { Thread.sleep(new Random().nextInt(4000)); } 
18
         catch (InterruptedException e) { } 
19
         System.out.println("pong");
20
      }
21
   }
22
}



And the instantiation of the thread is passing by the Runnable implementation in its constructor.

Java




xxxxxxxxxx
1
10


 
1
public class MainClass {
2
   public static void main(String[] args) {
3
      System.out.println("Start of main thread");
4
      Thread ping = new Thread(new Ping());
5
      Thread pong = new Thread(new Pong());
6
      ping.start();   // async
7
      pong.start();   // async
8
      System.out.println("End of main thread");
9
   }
10
}



Again, there is no guarantee of the execution order.

Since the Runnable interface is a functional interface, we can use a lambda expression to create the Runnable instance. The readability of the code gets a little messy (in my opinion), but still, it is a way to go.

Java




xxxxxxxxxx
1
22


 
1
public class MainClass {
2
   public static void main(String[] args) {
3
      System.out.println("Start of main thread");
4
      Thread ping = new Thread(() -> {
5
         for (int i = 0; i < 3; i++) {
6
            try { Thread.sleep(new Random().nextInt(2000)); } 
7
            catch (InterruptedException e) { } 
8
            System.out.println("ping");
9
         }
10
      });
11
      Thread pong = new Thread(() -> {
12
         for (int i = 0; i < 3; i++) {
13
            try { Thread.sleep(new Random().nextInt(2000)); } 
14
            catch (InterruptedException e) { } 
15
            System.out.println("pong");
16
         }
17
      });
18
      ping.start();   // async
19
      pong.start();   // async
20
      System.out.println("End of main thread");
21
   }
22
}



Threads Attributes and Methods

To ensure that the main thread finishes its execution after the worker threads, we can join it, pausing its execution while the worker threads are alive.

Java




xxxxxxxxxx
1
25


1
public class MainClass {
2
   public static void main(String[] args) throws InterruptedException {
3
      System.out.println("Start of main thread");
4
      Thread ping = new Thread(() -> {
5
         for (int i = 0; i < 3; i++) {
6
            try { Thread.sleep(new Random().nextInt(2000)); } 
7
            catch (InterruptedException e) { } 
8
            System.out.println("ping");
9
         }
10
      });
11
      Thread pong = new Thread(() -> {
12
         for (int i = 0; i < 3; i++) {
13
            try { Thread.sleep(new Random().nextInt(2000)); } 
14
            catch (InterruptedException e) { } 
15
            System.out.println("pong");
16
         }
17
      });
18
      ping.start();
19
      pong.start();
20
      ping.join();
21
      pong.join();
22
      // will be printed last:
23
      System.out.println("End of main thread"); 
24
   }
25
}



The output may be as follows, and it's guaranteed that the last line will be the “End of main thread” in the example above.

Java




xxxxxxxxxx
1


1
Start of main thread
2
pong
3
pong
4
ping
5
pong
6
ping
7
ping
8
End of main thread



Threads have an attribute, name, that we can use to identify them during program execution. The default name is Thread-n, where ’n’ is the incremental number of creation.

Java




xxxxxxxxxx
1


1
// setting thread name by constructor or setter
2
new Thread(runnableInstance,"My Thread Name"); 
3
threadInstance.setName("Ping Thread");
4
// getting thread name
5
threadInstance.getName();



Thread priority is a numeric (integer) value associated with a thread that is taken into consideration by the scheduler when determining which thread should currently be executing. There are static constants that help us out:

  • Thread.MIN_PRIORITY // int value = 1
  • Thread.NORM_PRIORITY // int value = 5, it's the default value
  • Thread.MAX_PRIORITY // int value =10

An issue to be aware of is the synchronization between data accessed by threads. If a value is accessed by many threads to make computation, its results can vary depending on the concurrent read made.

For example, let's assume a simple counter class:

Java




xxxxxxxxxx
1


 
1
class Counter {
2
   int count;
3
   public void increment() {
4
      count++; // count = count + 1
5
   }
6
}



And the main class that consumes it using two async threads:

Java




xxxxxxxxxx
1
22


 
1
public class MainClass {
2
   public static void main(String[] args) throws InterruptedException {
3
      Counter counter = new Counter();
4
      
5
      Thread c1 = new Thread(() -> {
6
         for (int i = 0; i < 500; i++) {
7
            counter.increment();
8
         }
9
      });
10
      Thread c2 = new Thread(() -> {
11
         for (int i = 0; i < 500; i++) {
12
            counter.increment();
13
         }
14
      });
15
      c1.start(); // async counter thread 1 start
16
      c2.start(); // async counter thread 1 start
17
      // awating async threads to finish
18
      c1.join();
19
      c2.join();
20
      System.out.println(counter.count); 
21
   }
22
}



The output varies on a number equals or less than 1000. The issue is on the ‘cont’ attribute that can be incremented by the other thread while it's being read by the first one. To ensure that just one thread will be executing the  increment() method, we can use the synchronized keyword, so we always get the correct sum of values (‘1000’, in this case).

Java




xxxxxxxxxx
1


 
1
class Counter {
2
   int count;
3
   public synchronized void increment() {
4
      count++; // count = count + 1
5
   }
6
}



Interface ExecutorService and Threads/Callables

The ExecutorService interface is the easiest and recommended way to create and manage async tasks. We must first obtain an instance of an ExecutorService by using some factory methods and then send the service tasks to be processed.

To instantiate an ExecutorService, we should use one of the Executors static methods, depending on the way we want to schedule the tasks and the number of threads used to do the work.

  • Executors.newSingleThreadExecutor(): Creates a single-threaded executor. Results are processed sequentially.
  • Executors.newFixedThreadPool(int nrOfThreads): Creates a thread pool that reuses a fixed number of threads.
  • Executors.newCachedThreadPool(): Creates a thread pool that creates new
    threads as needed, but will reuse previous threads when they are available.
  • Executors.newSingleThreadScheduledExecutor(): Creates a single-threaded executor that can schedule commands to run after a given delay or to execute periodically.
  • Executors.newScheduledThreadPool(int nrOfThreads): Creates a thread pool that can schedule commands to run after a given delay or to execute periodically.

First, we create a Runnable implementation:

Java




xxxxxxxxxx
1


 
1
class TaskRunn implements Runnable {
2
   @Override
3
   public void run() {
4
      System.out.println("I am " + 
5
          Thread.currentThread().getName());
6
   }
7
}



And then, we can assign the work to an ExecutorService to manage:

Java




xxxxxxxxxx
1
11


 
1
public class MainClass {
2
   public static void main(String[] args) {
3
      // pool of 4 threads
4
      ExecutorService executorService = Executors.newFixedThreadPool(4);
5
      // creates 4 runnables to be executed
6
      for (int i = 0; i < 4; i++) {
7
         TaskRunn runnable = new TaskRunn();
8
         executorService.execute(runnable);
9
      }
10
      executorService.shutdown(); // shuts down the executor service
11
}



The output may be (no order guaranteed):

Java




xxxxxxxxxx
1


1
I am pool-1-thread-2
2
I am pool-1-thread-4
3
I am pool-1-thread-1
4
I am pool-1-thread-3 



When we need to get a returned value, we should implement a Callable instead of a Runnable. The  Callable interface has the call() method that returns a value and can throw a checked exception.

Java




xxxxxxxxxx
1


 
1
class TaskCall implements Callable<String> {
2
   @Override
3
   public String call() throws Exception {
4
      return "I am " + Thread.currentThread().getName();
5
   }
6
}



When invoking a Callable task within ExecutorService, we get a Future object, which represents the result of async computation when it is over:

Java




xxxxxxxxxx
1
19


 
1
public class MainClass {
2
   public static void main(String[] args) {
3
      // pool of 4 threads
4
      ExecutorService executorService = Executors.newFixedThreadPool(4);
5
      List<Future<String>> futures = new ArrayList<>();
6
      // creates 4 callables to be executed
7
      for (int i = 0; i < 4; i++) {
8
         TaskCall callable = new TaskCall();
9
         Future<String> future = executorService.submit(callable);
10
         futures.add(future);
11
      }
12
      // getting tasks results
13
      futures.forEach(f -> {
14
         try { System.out.println(f.get()); }
15
         catch (Exception e) { }
16
      });
17
      
18
      executorService.shutdown(); // shuts down the executor service
19
}



We can use the invokeAll() method to send a list of callables to be executed asynchronously:

Java




xxxxxxxxxx
1
16


 
1
public class BasicConcurrencyTests {
2
   public static void main(String[] args) 
3
      throws InterruptedException {
4
      // pool of 4 threads
5
      ExecutorService executorService = Executors.newFixedThreadPool(4);
6
      // creates 3 callables to be executed
7
      List<Callable<String>> callables = Arrays.asList(
8
            new TaskCall(), new TaskCall(), new TaskCall());
9
      List<Future<String>> futures = executorService.invokeAll(callables);
10
      // getting tasks results
11
      futures.forEach(f -> { 
12
         try { System.out.println(f.get()); }
13
         catch (Exception e) { }
14
      });
15
      executorService.shutdown(); // shuts down the executor service
16
}



Example of output:

Java




xxxxxxxxxx
1


1
I am pool-1-thread-1
2
I am pool-1-thread-2
3
I am pool-1-thread-3 



There is a lot more to cover, like thread-safe data structures and scheduled tasks, but I guess this content covers the very basics of Java concurrent API.

Further Reading

[DZone Refcard] Core Java Concurrency

Java Concurrency in Depth

Java Concurrency: Threads

Topics:
java ,concurrency ,threads

Published at DZone with permission of Tiago Albuquerque . See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}