DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Mastering Thread-Local Variables in Java: Explanation and Issues
  • Java EE 6 Pet Catalog with GlassFish and MySQL
  • Java, Spring Boot, and MongoDB: Performance Analysis and Improvements
  • Getting Started With JPA/Hibernate

Trending

  • Understanding and Mitigating IP Spoofing Attacks
  • Performance Optimization Techniques for Snowflake on AWS
  • Build an MCP Server Using Go to Connect AI Agents With Databases
  • Teradata Performance and Skew Prevention Tips
  1. DZone
  2. Data Engineering
  3. Data
  4. Java Thread Synchronization and Concurrency Part 1

Java Thread Synchronization and Concurrency Part 1

By 
Sunil P V user avatar
Sunil P V
·
Updated Feb. 25, 20 · Tutorial
Likes (13)
Comment
Save
Tweet
Share
20.6K Views

Join the DZone community and get the full member experience.

Join For Free

Introduction

Java thread synchronization and concurrency are the most discussed topics during various design phases of a complex application. There are many aspects of threads, synchronization techniques to achieve great concurrency in an application. The evolution of CPUs over the years (multi-core processors, registers, cache memory, and the main memory (RAM) has lead to some areas that usually developers tend to overlook — like thread context, context-switching, variable visibility, JVM memory model vs CPU memory model. 

In this series, we will discuss various aspects of the Java memory model, including how it impacts thread contexts, synchronization techniques in Java to achieve concurrency, race conditions, etc. In this article, we'll focus on the concepts of threads, synchronization techniques, and the memory models of both Java and our CPU.

Recapitulation

Let's have a quick recap on some thread-related terminologies and concepts before we delve deep into this topic of threads and synchronization further.

  1. Lock — a lock is a thread synchronization mechanism.
  2. Every object in Java has an intrinsic lock associated with it. Threads use the Object's monitor to lock or unlock. A lock can be considered as data that is logically a part of the Object's header in memory. See ReentrantLock for extended capabilities that the monitor cannot achieve.
  3. Every Object in Java has synchronization methods, wait() and notify() [also notifyAll()]. Any thread calling these methods obtains a lock on that Object using its monitor. This has to be called using synchronized keyword else and IllegealMonitorStateException will be thrown.
  4. A signal is a way to notify a thread that it should continue its execution. This is achieved using the Object methods wait(), notify(), and notifyAll(). Calling the methods, notify() or notifyAll(), singals the thread(s) to wake up what is in the background (by a call to the method, wait()).
  5. Missed signal - The methods notify() and notifyAll() do not save the method calls, nor are they aware if wait() has been called or not by other threads. If a thread calls notify() before the thread to be signaled has called wait(), the signal will be missed by the waiting thread. This may cause a thread to wait endlessly because it missed a signal.
  6. Runnable is a functional interface that can be implemented by any class in an application so that a thread can execute it.
  7. volatile is another keyword assigned to variables to make classes thread-safe. To understand the usage of this keyword, the CPU architecture and JVM memory model has to be understood. We'll cover this later. 
  8. ThreadLocal enables the creation of variables that can only be read/written by the owner thread. This is used to make code thread-safe.
  9. Thread Pool is a collection of threads, where the threads will be executing tasks. The creation and maintenance of threads are very controlled by a service. In Java, a thread pool is represented by an instance of the ExecutorService.
  10. ThreadGroup is a that class provides a mechanism for collecting multiple threads into a single object and allows us to manipulate/control those threads all at once.
  11. Daemon thread — These threads run in the background. A good example of daemon thread is the Java Garbage Collector. The JVM does not wait for a daemon thread before exiting to complete its execution (while JVM waits for non-daemon threads or user threads to finish it's execution).
  12. synchronized — keyword to control the code execution by a single thread when various threads have to execute the same piece of functionality in a concurrent mode. This keyword can be applied for methods and for code blocks to achieve thread-safety. Note that there is no timeout for this keyword, so there is potential for dead-lock situations.
  13. Dead-lock - a situation wherein one or more threads are waiting for an object lock to be released by another thread. A possible scenario that causes dead-locks could be where threads are waiting for each other to release the lock!
  14. Spurious wake-ups - For inexplicable reasons, it is possible for threads to wake up even if notify() and notifyAll() have not been called. This is a spurious wake-up. To cover this issue, the thread awakened spins around a condition in the spin lock.
Java
 




xxxxxxxxxx
1
11


 
1
public synchronized doWait() {
2
  while(!wasSignalled) { // spin-lock check to avoid spurious wake up calls
3
    wait();
4
  }
5
  // do something
6
}
7
    
8
public synchronized doNotify() {
9
  wasSignalled = true;
10
  notify();
11
}


Thread Starvation

Thread starvation occurs when a thread is not granted CPU time because other threads are hogging all of it. (E.g. threads waiting on an object (that has called wait()) remain waiting indefinitely because other threads are constantly awakened instead (by calling notify()). 

In order to mitigate such conditions, we can set a priority for a thread, using the Thread.setPriority(int priority) method. The priority parameter has to be within a set range between Thread.MIN_PRIORITY to Thread.MAX_PRIORITY. Check the official Thread documentation for more information on thread priority.

You may also like: Java Thread Tutorial: Creating Threads and Multithreading in Java

Lock Interface vs Synchronized Keyword

  1. Having a timeout in a synchronized block or method is not possible. This could end up in scenarios where the application appears to be hung, in a dead-lock, etc. A synchronized block must be contained within a single method only.
  2. An instance of the Lock interface can have its calls to lock() and unlock() in separate methods. Additionally, Locks can also have timeouts as well. These are two great benefits when compared to the synchronized keyword.

The following is a simple implementation of a custom lock class using native wait() and notify() methods. Please read the comments in the code block below, which gives more information on the wait() and notify() methods.

Java
 




xxxxxxxxxx
1
22


 
1
class CustomLock {
2
 
          
3
  private boolean isLocked = false;
4
 
          
5
  public synchronized void lock() 
6
            throws InterruptedException {
7
    
8
    isLocked = true;
9
    while(isLocked) {
10
      // calling thread releases the lock it holds on the monitor
11
      // object. Multiple threads can call wait() as the monitor is released.
12
      wait();
13
    }
14
  }
15
 
          
16
  public synchronized void unlock() {
17
    isLocked = false;
18
    notify();
19
    // only after the lock is released in this block, the wait() block
20
    // above can re-acquire the lock on this object's monitor.
21
  }
22
}



Thread Execution

There are two ways by which we can execute a thread in Java. They are:

  1. Extending the Thread class and calling the start() method. (This is not a preferred way of sub-classing a class from Thread, as it reduces the scope of adding more functionality of the class.)
  2. Implementing the Runnable or Callable interface. Both the interfaces are functional interfaces, which means that both have exactly one abstract method defined. (A preferred approach as a class can be extended in the future by implementing other interfaces as well.)

Runnable Interface

This is a fundamental interface used to execute a particular task by a thread. This interface describes only one method, called run() with a void return type. Implement this interface if any functionality has to be executed in a thread but there is no return type expected. Basically, the result of the thread or any exception or error cannot be retrieved in cases of failure.

Callable Interface

This is an interface used to execute a particular task by a thread in addition to get a result of execution. This interface follows generics. It describes only one method, called call(), with a return type described, per the class that implements this interface. Implement this interface if any functionality has to be executed in a thread and the result of the execution has to be captured.

Synchronization Techniques

As described above, a thread can be synchronized using the synchronized keyword or by using an instance of a Lock. A fundamental implementation of the Lock interface is the ReentrantLock class. Also, there are variations of the Lock interfaces for read/write operations. 

This helps the application to achieve higher concurrency when threads are attempting to read or write to a resource. This implementation is called ReentrantReadWriteLock. The major differences between the two classes are shown below:

Class ReentrantLock Class ReentrantReadWriteLock
Give access to only 1 thread for either reading or writing but not both. Gives access to multiple/all threads at a time if the opeartion is reading a resource.
Only one thread at a time will be given access if the operation is a write.
Locks a resource for both read and write operations making the operations mutually exclusive. Has separate locks for read and write operations.
Reduces performance as the resource is locked even for read operations. Better in terms of performance as it gives concurrent acces to all threads who want to perform read operations.


See the ReentrantReadWriteLock example below to see how to achieve concurrent reads on a resource while allowing only one thread to update a resource. 

Note: a resource can be any data that various threads in an application try to access concurrently.

Java
 




xxxxxxxxxx
1
20


 
1
public class ConcurrentReadWriteResourceExample {
2
    
3
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
4
    private ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
5
    private ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
6
    
7
    private void readResource() {
8
        readLock.lock();
9
        // read the resource from a file, cache, database or from memory
10
        // this block can be accessed by 'N' threads concurrently for reading
11
        readLock.unlock();
12
    }
13
    
14
    private void writeResource(String value) {
15
        writeLock.lock();
16
        // write or update value to either a file, cache, database or from memory
17
        // this block can be accessed by at-most '1' thread at a time for writing
18
        writeLock.unlock();
19
    }
20
}



Create one instance of the above class and pass it to multiple threads; the following will be handled:

  • Either the readLock is used by 'N' threads or writeLock is used by at-most one thread.
  • Never both a read or write happens at the same time.

Java Memory Model and CPU

A note on the Java and CPU memory models will help us better understand how objects and variables are stored in the Java Heap/Thread-stack vs the actual CPU memory. A modern-day CPU consists of Registers, which act as the direct memory of the processor itself, Cache Memory — every processor has a cache layer to store data, and finally the RAM or main memory, where application data is present.

JVM vs CPU memory model

JVM vs CPU memory model

On the hardware or CPU, both the Thread Stack and Heap are located in main memory. Parts of the Thread Stack and Heap may sometimes be present in the CPU Cache and in internal Registers. The following are issues that can occur due to the above architecture:

  1. Visibility of thread updates (writes) to shared variables are not immediately seen by all threads accessing the variables.
  2. Race conditions when reading, checking, and updating data of the shared variables.

Volatile Keyword

The volatile keyword was introduced in Java 5 and has significant use in achieving thread safety. This keyword can be used for primitives as well as objects. The usage of the volatile keyword on a variable ensures that the given variable is read directly from main memory and written back to the main memory when updated.

There is a very good article on volatile keyword in DZone. Please refer this link to gain better understanding on this concept and best use of it.

ThreadLocal Class

A final topic in thread synchronization is after Lock is the Java class, ThreadLocal. This class enables the creation of variables that can only be read/written by the same thread. This gives us a simple way to achieve thread safety by defining a thread local variable. ThreadLocal has significant usage in thread pools or the ExecutorService, so that each thread uses its own instance of some resource or object.

For example, for every thread, a separate database connection is required, or a separate counter is required. In such cases, ThreadLocal helps. This is also used in Spring Boot applications, where the user context is set for every incoming call (Spring Security), and the user context will be shared across the flow of the thread through various instances. Use ThreadLocal for the following cases:

  • Thread confinement.
  • Per thread data for performance.
  • Per thread context.
Java
 




xxxxxxxxxx
1
31


 
1
/**
2
 * This is a demo class only. The ThreadLocal snippet can be applied
3
 * to any number of threads and you can see that each thread gets it's
4
 * own instance of the ThreadLocal. This achieves thread safety.
5
*/
6
public class ThreadLocalDemo {
7
    
8
    public static void main(String...args) {
9
        ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
10
            protected String initialValue() {
11
                return "Hello World!";
12
            }
13
        };
14
        
15
        // below line prints "Hello World!"
16
        System.out.println(threadLocal.get());
17
        
18
        // below line sets new data into ThreadLocal instance
19
        threadLocal.set("Good bye!!!");
20
        
21
        // below line prints "Good bye!!!"
22
        System.out.println(threadLocal.get());
23
        
24
        // below line removes the previously set message
25
        threadLocal.remove();
26
        
27
        // below line prints "Hello World!" as the initial value will be
28
        // applied again
29
        System.out.println(threadLocal.get());
30
    }
31
}



That's it on thread synchronization and associated concepts. Concurrency will be covered in the part 2 of this article.


Further Reading

  • Intricacies of Multi-Threading in Java.
  • Top 15 Java Multithreading, Concurrency Interview Questions.
Java (programming language) Threading Database connection Memory (storage engine) Interface (computing) application Memory model (programming)

Opinions expressed by DZone contributors are their own.

Related

  • Mastering Thread-Local Variables in Java: Explanation and Issues
  • Java EE 6 Pet Catalog with GlassFish and MySQL
  • Java, Spring Boot, and MongoDB: Performance Analysis and Improvements
  • Getting Started With JPA/Hibernate

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!