In Java, a synchronized method is designed to access only one thread at a time. This is used to prevent multiple threads from concurrently executing a method or block of code that can lead to inconsistent or corrupted data.
Use case scenario.
Global Configuration Access.
The software developer designs the configuration after starting the server. A global configuration class that loads settings from a file or database must ensure that only one thread can simultaneously load or modify these settings.
public class GlobalConfig { private static Map<String, String> config = new HashMap<>(); public static synchronized void loadConfig() { // Code to load configuration from file/database } public static synchronized String getConfig(String key) { return config.get(key); } public static synchronized void setConfig(String key, String value) { config.put(key, value); } }
In another case, in a JSF web application, the developer should log each activity in the database when the client sets the configuration on individual servers simultaneously. Still, the problem is that the config value needs to be clarified because Configuration is an instance class that shares resources in an instance. When the developer retrieves values from the Configuration and inserts them into a database, the value is obtained from another client instead.
Depending on the project situation, the developer can change how a log is inserted into the database or use a synchronized method.
Simulate multiple clients in the admin role set configuration.
import java.util.Date; public class Config { public static void main(String[] args){ SetConfigThread thread_1 = new SetConfigThread("thread_1"); SetConfigThread thread_2 = new SetConfigThread("thread_2"); SetConfigThread thread_3 = new SetConfigThread("thread_3"); thread_1.start(); thread_2.start(); thread_3.start(); } } class SetConfigThread extends Thread { private String threadName; public SetConfigThread(String threadName) { this.threadName = threadName; } @Override public void run() { System.out.println(threadName+ " is running... "+new Date()); GlobalConfig.setConfig("name",threadName); System.out.println(threadName + " success "+GlobalConfig.getConfig("name")); } }
1. When executed without a synchronized method.
thread_3 is running... Wed Aug 14 15:31:27 ICT 2024 thread_2 is running... Wed Aug 14 15:31:27 ICT 2024 thread_1 is running... Wed Aug 14 15:31:27 ICT 2024 thread_2 success thread_2 thread_3 success thread_2 thread_1 success thread_2
2. When executed with a synchronized method.
thread_1 is running... Wed Aug 14 15:42:44 ICT 2024 thread_3 is running... Wed Aug 14 15:42:44 ICT 2024 thread_3 success thread_3 thread_2 is running... Wed Aug 14 15:42:44 ICT 2024 thread_2 success thread_2 thread_1 success thread_1
Conclusion
The value in a global configuration is not shuffled by another thread when the developer uses the synchronized method.
The developer manages the generation of order numbers.
The software developer’s design generates order numbers in individual clusters. To prevent duplicate running numbers. The software developer uses a synchronized method to avoid that problem.
@Service public class OrderService { private int orderNumber = 0; public synchronized int generateOrderNumber() { return ++orderNumber; } public synchronized Order createOrder(String orderName) { int orderNumber = generateOrderNumber(); Order order = new Order(); order.setOrderNumber("S01_"+orderNumber); order.setOrderName(orderName); // Save the order to the database // orderRepository.save(order); return order; } }
@Getter @Setter @ToString public class Order { private String orderNumber; private String orderName; }
Simulate a case where multiple clients request to create order.
import java.util.Date; public class CreateOrder { public static void main(String[] args) { OrderThread thread_1 = new OrderThread("thread_1"); OrderThread thread_2 = new OrderThread("thread_2"); OrderThread thread_3 = new OrderThread("thread_3"); OrderThread thread_4 = new OrderThread("thread_4"); OrderThread thread_5 = new OrderThread("thread_5"); OrderThread thread_6 = new OrderThread("thread_6"); thread_1.start(); thread_2.start(); thread_3.start(); thread_4.start(); thread_5.start(); thread_6.start(); } } class OrderThread extends Thread { private String threadName; public OrderThread(String threadName) { this.threadName = threadName; } @Override public void run() { System.out.println(threadName + " is running... " + new Date()); Order order = OrderService.createOrder(threadName); System.out.println(threadName + " value " + order.toString()); } }
1. When executed without a synchronized method.
thread_3 is running... Wed Aug 14 16:20:06 ICT 2024 thread_1 is running... Wed Aug 14 16:20:06 ICT 2024 thread_6 is running... Wed Aug 14 16:20:06 ICT 2024 thread_5 is running... Wed Aug 14 16:20:06 ICT 2024 thread_4 is running... Wed Aug 14 16:20:06 ICT 2024 thread_2 is running... Wed Aug 14 16:20:06 ICT 2024 thread_1 value orderNumber=S01_3, orderName=thread_1 thread_2 value orderNumber=S01_5, orderName=thread_2 thread_4 value orderNumber=S01_4, orderName=thread_4 thread_3 value orderNumber=S01_1, orderName=thread_3 thread_5 value orderNumber=S01_1, orderName=thread_5 thread_6 value orderNumber=S01_2, orderName=thread_6
Test result
The order number had a duplicate between thread_3 and thread_5, getting the same value order number “S01_1”.
When the developer doesn’t use a synchronized method.
2. When executed with a synchronized method.
thread_4 is running... Wed Aug 14 16:20:29 ICT 2024 thread_1 is running... Wed Aug 14 16:20:29 ICT 2024 thread_5 is running... Wed Aug 14 16:20:29 ICT 2024 thread_3 is running... Wed Aug 14 16:20:29 ICT 2024 thread_2 is running... Wed Aug 14 16:20:29 ICT 2024 thread_6 is running... Wed Aug 14 16:20:29 ICT 2024 thread_3 value orderNumber=S01_4, orderName=thread_3 thread_4 value orderNumber=S01_1, orderName=thread_4 thread_2 value orderNumber=S01_5, orderName=thread_2 thread_1 value orderNumber=S01_3, orderName=thread_1 thread_6 value orderNumber=S01_6, orderName=thread_6 thread_5 value orderNumber=S01_2, orderName=thread_5
Test result
The order number is unique when the developer uses a synchronized method.
The developer is managing to print orders in the Windows application.
The software developer designed a printer that prints and waits until the current page prints successfully before printing the next page.
public class Printer { // Synchronized method to ensure only one thread can print at a time public synchronized void printOrder(String order) { System.out.println("Printing order: " + order); try { // Simulate the time taken to print an order Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Order printed: " + order); } }
Simulate a user requesting the printer to print the report.
public class PrintOrder { public static void main(String[] args) { Printer printer = new Printer(); // Create multiple threads simulating multiple orders being printed PrintThread order1 = new PrintThread(printer, "Order #1"); PrintThread order2 = new PrintThread(printer, "Order #2"); PrintThread order3 = new PrintThread(printer, "Order #3"); PrintThread order4 = new PrintThread(printer, "Order #4"); PrintThread order5 = new PrintThread(printer, "Order #5"); PrintThread order6 = new PrintThread(printer, "Order #6"); // Start the threads order1.start(); order2.start(); order3.start(); order4.start(); order5.start(); order6.start(); } } class PrintThread extends Thread { private Printer printer; private String order; public PrintThread(Printer printer, String order) { this.printer = printer; this.order = order; } @Override public void run() { printer.printOrder(order); } }
1. When executed without a synchronized method.
Printing order: Order #1 Printing order: Order #3 Printing order: Order #4 Printing order: Order #6 Printing order: Order #5 Printing order: Order #2 Order printed: Order #5 Order printed: Order #1 Order printed: Order #6 Order printed: Order #2 Order printed: Order #4 Order printed: Order #3
Test result
The printer method immediately begins printing the current page successfully.
2. When executed with a synchronized method.
Printing order: Order #1 Order printed: Order #1 Printing order: Order #6 Order printed: Order #6 Printing order: Order #5 Order printed: Order #5 Printing order: Order #4 Order printed: Order #4 Printing order: Order #3 Order printed: Order #3 Printing order: Order #2 Order printed: Order #2
Test result
The printer method waits for the current page to print successfully.
In another case, the customer found a problem when sending a request simultaneously to the printer without using a synchronized method. Still, the printer doesn’t print the report in the requested order.
Conclusion
Using the synchronized method, the printer can print the report following the requested order.
Deadlock Situation when using the synchronized method.
The software developer must be cautious when using synchronized methods. Sometimes, they may cause a serious deadlock, as it freezes the application and prevents it from working correctly.
class ResourceA { // Synchronized method to lock ResourceA and try to lock ResourceB public synchronized void methodA(ResourceB resourceB) { System.out.println(Thread.currentThread().getName() + ": Locked ResourceA, trying to lock ResourceB..."); // Simulate some work with a sleep try { Thread.sleep(100); } catch (InterruptedException e) {} resourceB.methodB(); // This will try to lock ResourceB } public synchronized void methodB() { System.out.println(Thread.currentThread().getName() + ": Locked ResourceA again."); } }
class ResourceB { // Synchronized method to lock ResourceB and try to lock ResourceA public synchronized void methodA(ResourceA resourceA) { System.out.println(Thread.currentThread().getName() + ": Locked ResourceB, trying to lock ResourceA..."); // Simulate some work with a sleep try { Thread.sleep(100); } catch (InterruptedException e) {} resourceA.methodB(); // This will try to lock ResourceA } public synchronized void methodB() { System.out.println(Thread.currentThread().getName() + ": Locked ResourceB again."); } }
Simulate a case for a deadlock situation.
public class DeadlockExample { public static void main(String[] args) { // Create the resources ResourceA resourceA = new ResourceA(); ResourceB resourceB = new ResourceB(); // Thread 1 tries to lock ResourceA and then ResourceB Thread thread1 = new Thread(() -> { resourceA.methodA(resourceB); }, "Thread 1"); // Thread 2 tries to lock ResourceB and then ResourceA Thread thread2 = new Thread(() -> { resourceB.methodA(resourceA); }, "Thread 2"); // Start both threads thread1.start(); thread2.start(); } }
1. When executed with a synchronized method.
Thread 2: Locked ResourceB, trying to lock ResourceA... Thread 1: Locked ResourceA, trying to lock ResourceB...
Test result
The process causes a deadlock—Resource B and Resource A wait for each other, which causes infinite waiting.
Conclusion
The developer must be cautious about the deadlock problem in the synchronized method. It caused an entire application to malfunction.
Finally
The synchronized method supports various situations and benefits developers by managing the process in order. The developer can benefit from it when applying to an application project, but the developer should be cautious of a deadlock situation. The synchronized method has pros and cons. The developer must consider whether it is suitable for the project.