Home  >  Blog  >   Java  > 

What is Java Concurrency?

Java Concurrency is one of the powerful features of the Java platform. Using Java Concurrency, you can run multiple Java programs or parts of a program simultaneously. As a result, it enhances the execution performance of processors and saves time significantly. Okay! Are you a learner curious to know more about Java Concurrency? Well! This well-versed tutorial will help you to learn much more about Java Concurrency in one place. Let’s go to the blog!

Rating: 4.7
  
 
281

Java Concurrency is the capability of the Java platform to run multiple operations simultaneously. The operations could be multiple Java programs or parts of a single Java program. Java Concurrency relies on two essential components such as threads and processes. Of the two components, threads play a significant role. With Java Concurrency or using multiple threads, you can enhance the performance of processors. You don’t need multiple processors for running concurrent programs in Java; instead, you can use a single multi-core processor. Know that a multi-core processor will have many cores in a single CPU. All these multiple cores can run many programs or parts of a program simultaneously.

We have designed this tutorial for you to learn more about Java Concurrency. In the way ahead, you will quickly learn about processes and threads, thread objects, Java Concurrency models, synchronization, liveliness, immutable objects, and high-level concurrency. Let’s dive into the topics.

Java Concurrency Tutorial - Table of Contents

If you want to enrich your career and become a professional in Java, then enroll in "Core Java Training". This course will help you to achieve excellence in this domain.

What is Concurrency Software?

Software that can run multiple operations simultaneously is referred to as concurrency software. In other words, the software can run many programs or sections of the same program in parallel. You can consider a word processor if you want to relate the concept of concurrency software with a real-time example. A word processor can format texts, respond to mouse and keyboard operations, and print documents simultaneously. Similarly, an audio streaming application is another example of concurrency because audio applications can read the digital audio from networks, playback, decompress it, and update the display – notably, all at the same time.

What is Java Concurrency?

As you know, Java is one of the popular programming languages. It supports concurrency with its Java class libraries and high-level concurrency APIs. Package Java .util.concurrent is the package used for concurrent programming in Java. This package consists of classes with useful functionalities, standardised small and extensible frameworks, and many more. Additionally, Java comes with packages such as Package Java.util.concurrent.locks and Java.util.concurrent.atomic packages. With these frameworks, packages, and classes, you can run long and complex Java programs using threads. Or, in other words, you can run concurrent programs. As a result, you can save time and increase throughput significantly by running a program using Java Concurrency.

What are Processes and Threads?

Processes and Threads are the essential elements of Java Concurrency. Mainly, they support creating an advanced execution environment in Java. In a way, it is a self-contained environment.

Let’s look at them in detail below:

  1. Processes: A process is nothing but a complete and private set of runtime resources. Each process has its own memory. Pipes and sockets help to communicate between processes in a system. Not only that, they encourage communication between different systems as well. Note that pipes and sockets are known as inter-process communication elements.
  2. Threads: They are the core of Java Concurrency and exist inside a process. Every process will have at least one thread. In a way, a thread is a virtual CPU where you can run Java codes. An application can have many threads and run them concurrently. Generally, threads follow the priority. So, the thread that has high-priority runs first. Know that threads use the same resources used by processes. The resources may be memory or open files, or any other. Besides, creating a thread requires fewer resources than creating a process.

What are Thread Objects?

Know that each thread is connected with an instance of the Thread class. Java Concurrency offers two methods through which you can create concurrency in applications with thread objects. If you want to control thread creation and management directly, you have to instantiate the Thread class whenever the application requires initiating an asynchronous task. Similarly, if you want to abstract the thread creation and management from the rest of the application, you must pass the applications' tasks to executors.

Let’s discuss the use of thread objects below:

  • Starting and Pausing Threads: Applications can create instances of Threads and run codes in the threads. It is achieved in two ways – providing a Runnable object and using a subclass Thread. When it comes to pausing threads, the Thread.sleep object is used to suspend the current thread's execution for a defined period. During this pause period, other threads can use processors. Even other applications can use the processors during this pause period.
  • Interrupts and Joins: In Java Concurrency, you can apply interrupts to stop the operations of threads and direct them to perform other tasks. When an interrupt is applied, it enables an interrupt flag to communicate the interrupt status. Here, the object Thread.interrupt is used to set the flag. If you want to clear the flag status, you need to invoke Thread.interrupted, which is a static method. Similarly, isInterrupted is the non-static method used by one thread to query the interrupt status of another thread. Moreover, you can use the join method to make one thread wait for another until it completes its task.

[ Check out Multithreading in Java ]

What are the Different Java Concurrency Models?

Essentially, concurrency models describe the different ways of communication that can be taken place between threads. There are three basic models used in Java Concurrency, as discussed below:

1. Parallel Workers Model

In this model, the main program is divided into many subprograms. These subprograms will run across different threads or workers of the model in parallel. Here, each thread or worker will run a different task. The main thing about this model is that it doesn't allow a single thread or worker to be idle. In other words, every thread or worker will be running a part of a program.

2. Assembly Line Model

This model is also known as a Reactive or Event-driven model. In this model, you can run tasks across the different threads in the assembly line. Every thread or worker in the assembly line will have a different task but run sequentially in one direction. Once a task is completed by a thread, it will be moved to the following thread. Know that this model uses non-blocking I/O. According to this model, the CPU doesn’t waste time running the I/O operation. Note that you must use multiple assembly lines to execute a complete program.

3. Functional Parallelism

This model uses multiple CPUs to run multiple tasks simultaneously. It is accomplished by using function calls. Here, every function call runs independently. It means that function calls are executed in separate CPUs.

What is Synchronization in Java Concurrency?

Generally, threads communicate by sharing access to fields and the content that object reference fields refer to. But, it creates two types of errors: thread interference and memory consistency. Synchronization is the tool that helps to overcome these errors.

At first, let’s discuss thread interference and memory consistency errors as below:

  • Thread Interference: It is a simple but vital note that this error occurs because of interleaving. It means that two operations in two different threads act on the same data. In other words, this error occurs when sequences of steps in two threads overlap with each other.
  • Memory Consistency Error: This error occurs when threads have inconsistent views about what must be the ‘same data’. In fact, the causes for this error are complex. And this error can be overcome by understanding the ‘happens-before’ relationship. This relationship ensures that memory writes done by one statement are visible to another. Know that synchronization is one of the actions that create ‘happens-before’ relationships. That’s why it is used to overcome memory consistency errors.

If you are wondering how to overcome these errors, read the following. Here, we will see synchronized methods and how they prevent the above-said errors.

In Java Concurrency, synchronized methods help to prevent thread interference and memory inconsistency error. When you make reads and writes of an object’s variables through synchronized methods, you can make the object visible to more than one thread. As a result, you can overcome the errors mentioned effectively.

Atomic access is one of the crucial aspects of synchronization. Let’s see now what atomic access is.

In Java Concurrency, atomic action occurs either entirely or not at all. You cannot see any side effects of atomic action until it is entirely run. For instance, reads and writes are atomic actions for reference and primitive variables except for long and double variables. Similarly, reads and writes are atomic actions for all volatile variables, including long and double variables.

MindMajix Youtube Channel

What is Liveness in Java Concurrency?

Essentially, liveness is the ability of concurrent applications to execute threads on time – without delays and locks. However, it is not possible all the time in Java Concurrency. When two or more threads try to access the same resource, it creates thread contention. As a result, the Java runtime executes some threads more slowly or suspends the execution of threads.

Know that Starvation and livelock are the forms of thread contention. Let’s have a brief about them below:

  1. Deadlock: It is the situation in which two or more threads are blocked at any time. They will no longer operate further. This is because the threads wait for each other endlessly.
  2. Starvation and Livelock: They don’t create serious issues like a deadlock. However, they need to be rectified quickly to continue program execution. Starvation is the situation in which threads cannot access shared resources. As a consequence, they cannot progress further. This is because of greedy threads that use shared resources for long times. Hence, the shared resources won't be available for other threads during Starvation. Another form of thread contention, livelock, is the undesired situation that occurs when threads simultaneously respond to each other's actions. In a way, threads are not blocked in a livelock situation, but they will be busy responding to each other’s actions.

What are Immutable Objects in Java Concurrency?

Immutable objects don’t change their state once they have been built. Because of this feature, they are not affected by thread interference. And, they don’t move to an inconsistent state at any time.

Now, the question is how always to keep immutable objects unchanged. You can get the answer below.

You can follow some strategies to define immutable objects in Java Concurrency. Let’s see them below:

  • You shouldn’t use the methods that could change object fields.
  • It would help if you made all object fields private and final.
  • When instance fields include references to mutable objects, you shouldn’t allow mutable objects to change.

[ Check out Top Java Frameworks List ]

What are High-level Concurrency Objects?

By using high-level building blocks, we can run massive concurrent applications smoothly. As a result, we can fully utilise multiprocessors and multi-core systems. Let's explore a few high-level concurrency objects below:

1. Lock Objects:

Know that only one thread can use a lock object simultaneously. Lock objects support the wait or notify mechanism with the help of their condition objects. The great thing about lock objects is that they can back out the attempts to access a lock. The tryLock method backs out when the lock is unavailable immediately. Similarly, the lockInterruptibly method backs out when another thread sends an interrupt before acquiring the lock.

2. Executors:

It is always a better strategy to separate ‘thread creation and thread management’ from the rest of the application to achieve better performance. This strategy must be done while executing large-scale applications. This way, objects that consist of functions to achieve this strategy are known as executors.

In Java Concurrency, executors define high-level APIs for launching and managing threads. They have three essential components: thread pools, executor interfaces, and Fork/Join. At first, executor interfaces provide three executor object types: executor, executor service, and scheduledexecutorservice. Next, thread pools consist of worker threads used to run multiple tasks. They help minimise overheads that may occur while creating threads. The overhead occurs since thread objects consume a significant amount of memory. The third one, Fork/Join, is a framework that supports running multiple processors effectively.

This framework breaks tasks into small pieces and utilises all processors. As a result, executing performance of processors is enhanced remarkably in Java Concurrency.

3. Concurrent Collections:

They are yet another high-level concurrency object of Java Concurrency. They help to manage large volumes of data efficiently, reducing the need for synchronization.

4. Atomic Variables:

In Java Concurrency, they reduce the need for synchronization and eliminate memory inconsistency errors. Especially, the Java .util.concurrent.atomic package consists of classes that help to make atomic operations on single variables.

Learn Java Concurrency Interview Questions and Answers that help you grab high-paying jobs

How to Define and Start a Thread in Java Concurrency?

Generally, applications can create instances of threads seamlessly, but they should provide codes to run on the threads. It can be done in two methods in Java Concurrency:

  • Providing a Runnable object.
  • Using a Subclass Thread.

Method 1: Providing a Runnable Object

The runnable interface provides a method known as the run. This method contains the code that will be executed in the thread. Then, the runnable objects are moved to the thread constructor. Below is an example that explains this concept of Java Concurrency.

public class HelloRunnable implements Runnable {

    public void run() {
        System.out.println("Hello from a thread!");
    }

    public static void main(String args[]) {
        (new Thread(new HelloRunnable())).start();
    }

}

Method 2: Using a Subclass Thread

The thread class can implement Runnable even if the run method doesn’t act. In this case, an application will subclass thread. And it provides its own implementation of the run method. The following example can explain this concept in Java Concurrency.

public class HelloThread extends Thread {

    public void run() {
        System.out.println("Hello from a thread!");
    }

    public static void main(String args[]) {
        (new HelloThread()).start();
    }

}

How to Pause Execution with Sleep in Java Concurrency?

You can pause the current execution of a thread for a specified time using Thread. Sleep in Java Concurrency. This way, you can make the processors available to other threads and applications.

Java offers two versions of sleep. One version specifies the sleep time as milliseconds, whereas another specifies sleep time as nanoseconds. Note that sleep times can be stopped by sending interrupts. Below is an example that explains printing messages at four-second time gaps using sleep.

public class SleepMessages {
    public static void main(String args[])
        throws InterruptedException {
        String importantInfo[] = {
            "Mares eat oats",
            "Does eat oats",
            "Little lambs eat ivy",
            "A kid will eat ivy too"
        };

        for (int i = 0;
             i < importantInfo.length;
             i++) {
            //Pause for 4 seconds
            Thread.sleep(4000);
            //Print a message
            System.out.println(importantInfo[i]);
        }
    }
}

How to use Atomic Variables in Java Concurrency?

No doubt that the counter class creates thread interference. However, it can be resolved by using synchronization and atomic operations. Synchronization used an integer that AtomicInteger can replace to avoid thread interference issues. The following example can show the use of atomic operations in Java Concurrency.

import java.util.concurrent.atomic.AtomicInteger;

class AtomicCounter {
    private AtomicInteger c = new AtomicInteger(0);

    public void increment() {
        c.incrementAndGet();
    }

    public void decrement() {
        c.decrementAndGet();
    }

    public int value() {
        return c.get();
    }

}

How to Kill Threads in Java Concurrency?

There are two ways to kill threads in Java. It can be done using flags and interrupts.

Using Flags:

A flag is used to show whether a thread is running or not. You can use this flag to take corrective actions based on requirements. The following code will help you to kill a thread using flags. Here, you can control execution by setting the Running variable as false. Also, you can use AtomicBoolean for concurrency as well.

public class KillThreadUsingFlag implements Runnable {
    private final AtomicBoolean running = new AtomicBoolean(false);
    private Thread thread;
  @Override
    public void run() {
        while (running.get()) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
            }
        }
        System.out.println("Shutting down thread");
    }
    public void shutdown() {
        running.set(false);
    }
    public void start() {
        thread = new Thread(this);
        thread.start();
    }
    public static void main(String[] args)
            throws InterruptedException {
        KillThreadUsingFlag process = new KillThreadUsingFlag();
        process.start();
        Thread.sleep(5000);
        process.shutdown();
    }
}

Using Interrupts:

The codes written based on flags usually will have some drawbacks. It will get complicated when there are many threads. So, you don’t need to use the Boolean flag while using the interrupted flag.

public class InterruptedExceptionExample implements  Runnable {
    private final AtomicBoolean running = new AtomicBoolean(true);
    @Override
    public void run() {
        try{
            while (running.get()){
                for(int i =0; i<3 ; i++){
                    System.out.println(i);
                }
                Thread.sleep(2000);
            }
        }
        catch (InterruptedException e){
            Thread.currentThread().interrupt();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<?> future = executor.submit(new InterruptedExceptionExample());
        Thread.sleep(3000);
        executor.shutdownNow();
    }
}

Conclusion

It’s a wrap! You might have understood that Java Concurrency enhances execution performance and saves time. And we hope this tutorial might have taught you Java Concurrency from A to Z. Remember, You have learned about processes and threads, thread objects, concurrency models, and much more in Java Concurrency. No doubt that the hands-on experience in defining and starting threads, pausing and killing them, must have increased your confidence in the subject to higher levels.

Join our newsletter
inbox

Stay updated with our newsletter, packed with Tutorials, Interview Questions, How-to's, Tips & Tricks, Latest Trends & Updates, and more ➤ Straight to your inbox!

Course Schedule
NameDates
Core Java TrainingNov 19 to Dec 04
Core Java TrainingNov 22 to Dec 07
Core Java TrainingNov 26 to Dec 11
Core Java TrainingNov 29 to Dec 14
Last updated: 17 November 2022
About Author
Madhuri Yerukala

Madhuri is a Senior Content Creator at MindMajix. She has written about a range of different topics on various technologies, which include, Splunk, Tensorflow, Selenium, and CEH. She spends most of her time researching on technology, and startups. Connect with her via LinkedIn and Twitter .

Recommended Courses

1 /15