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.
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.
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.
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.
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:
public long count = 0;
equal
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!