Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Java

Synchronized and Concurrent in Java

5.00/5 (2 votes)
12 Aug 2024CPOL3 min read 2.3K  
In Java, Synchronized and Concurrent are two fundamental concepts in multithreading
In this article, we will describe the concepts of Synchronized and Concurrent, their differences, and some common scenarios where Synchronized and Concurrent are used.

Reference: https://www.tuanh.net/blog/java/synchronized-and-concurrent-in-java

1. Synchronized

1.1 Concept

Synchronized is the traditional way to handle concurrency in programming. With Synchronized, only one thread can execute a specific block of code at a time.

Image

In the example above, we see that there are 3 threads that need to run. However, they do not run concurrently; instead, they take turns executing on the CPU. In reality, threads do not run continuously like this; there is a period of rest between thread switches, known as a Context Switch.

To further clarify the example above, I have an example of the Synchronized concept in Java.

Java
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;

public class Main {
     class MyRunnable implements Callable
       
       {
         public long count = 0;
         public synchronized void increment() {
             count++;
         }

        @Override
         public Long call() throws Exception {
             increment();
             return 1L;
         }
     }

    public static void main(String[] args) throws InterruptedException {
         Main main = new Main();
         main.synchronizedMethod();
     }

    public void synchronizedMethod() {
         int numberOfThreads = 1000000;

        CompletableFuture
       
       [] futures = new CompletableFuture[numberOfThreads];

        MyRunnable myRunnable = new MyRunnable();

        for (int i = 0; i < numberOfThreads; i++) {
             CompletableFuture
       
       future = CompletableFuture.supplyAsync(() -> {
                 try {
                     return myRunnable.call();
                 } catch (Exception e) {
                     e.printStackTrace();
                     return 0L;
                 }
             });
             futures[i] = future;
         }

        CompletableFuture
       
       allOf = CompletableFuture.allOf(futures);
         allOf.join();

        System.out.println("Count: " + myRunnable.count );
     }
 }

In the code snippet above, I use CompletableFuture with 1,000,000 threads to run the increment() method, which increments the count variable of the MyRunnable instance by one unit each time it runs. At the same time, I use the synchronized keyword in increment() to ensure that only one thread executes the method at a time. And here are the results when running it:

C:Program FilesJavajdk1.8.0_351
injava.exe
 Count: 1000000

Process finished with exit code 0

Of course, setting synchronized on increment() has synchronized the threads, ensuring that the count accurately reflects the number of threads.

2. Concurrent

This is the way Java allows multiple tasks to run simultaneously on different CPUs. This means that multiple tasks or processes are running and performing their work concurrently, without having to wait for one task to complete before another task can start.

Image

In the example above, we see that there are three threads that need to run. They are running on different CPUs and do not wait for each other. CPU in this context refers to a physical thread. For example, a computer with 4 cores and 4 threads per core will have 16 physical threads.

2.1 Illustration

To further clarify the example above, I have an example of the concept of Concurrent in Java. Let's take the example from section 1, but temporarily remove the synchronized keyword from the increment() method.

Java
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;

public class Main {
     class MyRunnable implements Callable
       
       {
         public long count = 0;
         public void increment() {
             count++;
         }

        @Override
         public Long call() throws Exception {
             increment();
             return 1L;
         }
     }

    public static void main(String[] args) throws InterruptedException {
         Main main = new Main();
         main.synchronizedMethod();
     }

    public void synchronizedMethod() {
         int numberOfThreads = 1000000;

        CompletableFuture
       
       [] futures = new CompletableFuture[numberOfThreads];

        MyRunnable myRunnable = new MyRunnable();

        for (int i = 0; i < numberOfThreads; i++) {
             CompletableFuture
       
       future = CompletableFuture.supplyAsync(() -> {
                 try {
                     return myRunnable.call();
                 } catch (Exception e) {
                     e.printStackTrace();
                     return 0L;
                 }
             });
             futures[i] = future;
         }

        CompletableFuture
       
       allOf = CompletableFuture.allOf(futures);
         allOf.join();

        System.out.println("Count: " + myRunnable.count );
     }
 }

And here are the results when running it:

C:Program FilesJavajdk1.8.0_351
injava.exe
 Count: 992916

Process finished with exit code 0

We see that the results between the two runs are not the same. The main reason for this difference is:

When you remove the synchronized keyword, you remove synchronization from the increment() method of the MyRunnable class. This means that multiple threads can execute at the same time and update the count value without synchronization. As a result, threads can read and write the count value without ensuring consistency.

When you use synchronized, only one thread can execute increment() at a time, ensuring consistency when incrementing the count value. It ensures that there is no contention between threads when accessing and updating the count variable.

When you remove synchronized, threads can compete and overwrite each other's count values, leading to inconsistent results.

For example, if two threads each increment count by 1, you might end up with a count value of 1 instead of 2. This happens because threads can access count simultaneously and write the same value without synchronization.

Another way to synchronize the count variable is by using AtomicLong. Instead of:

Java
public long count = 0;

equal

Java
public static AtomicLong count = new AtomicLong(0);

3. Conclusion

Through the above examples, I hope to provide you with an overview of Synchronized and Concurrent in Java, how they operate, and some illustrative examples. This concludes the article.

Thank you for reading! Happy coding!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)