Java Concurrency - Synchronized

2024. 11. 3. 20:42JAVA & SPRING/자바

반응형

The most typical one is access to shared data. In a multithreaded environment, conflicts occur when two or more threads try to update shared mutable data simultaneously. 

To solve this problem, a mechanism is provided. Logical fragments marked as "synchronized" become synchronization blocks, which ensures that at the same moment, only one thread can execute a certain method or a certain code block.

 

Three Ways to Use Synchronized


In Java, the synchronized keyword can be used in the following three ways:

 

1. Modify a normal method: The lock object is the current object. Before entering the synchronized code, you need to obtain the lock of the current object.

2. Modify a static method: The lock object is the current class object. Before entering the synchronized code, you need to obtain the lock of the current class object.

3. Modify a code block: The specified locking object is required, and the monitor lock of the specified object must be obtained before entering the synchronized code block.

 

Modify a normal method

public class InstanceMethodDemo {
    public int count = 0;

    public synchronized void increase(){
        count++;
    }

    public static void main(String[] args) throws InterruptedException {
        InstanceMethodDemo instanceMethodDemo = new InstanceMethodDemo();
        Runnable runnable = () -> {
            for (int i = 0; i < 10000; i++) {
                instanceMethodDemo.increase();
            }
        };

        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(instanceMethodDemo.count);
    }
}

 

Result:

20000

 

Instance methods are synchronized on the instance of the class that owns the method, which means that only one thread can execute the method at a time for each instance of the class.

 

 

Modify a static method

public class StaticMethodDemo {
    public static int count = 0;

    public static synchronized void increase() {
        count++;
    }

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () ->{
            for (int i = 0; i < 10000; i++) {
                increase();
            }
        };

        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

 

When synchronized is applied to a static method, the monitor lock that needs to be held is the Class object of the current class. 

 

public class MultiMethod {
    static int count = 0;

    static class StaticIncreaseThread implements Runnable {
        public static synchronized void increase() {
            count++;
        }

        public void run() {
            for (int i = 0; i < 10000; i++) {
                increase();
            }
        }
    }

    static class NonStaticIncreaseThread implements Runnable {
        public static synchronized void increaseNonStatic() {
            count++;
        }

        public void run() {
            for (int i = 0; i < 10000; i++) {
                increaseNonStatic();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new StaticIncreaseThread());
        Thread t2 = new Thread(new NonStaticIncreaseThread());

        t1.start();
        t2.start();

        t1.join();
        t2.join();
        System.out.println(count);
    }
}

If t1 calls a static synchronized method of the class which the instance object belongs to, while t2 calls a non-static synchronized method of the same instance object, this is permissible, and mutual exclusion will not occur.

 

This is because accessing a static synchronized method requires acquiring the lock on the class object of the current class, whereas accessing a non-static synchronized method requires acquiring the lock on the current instance object. Therefore, it will cause thread safety issues and result in the value of count not being 20000.

 

 

Modify a code block

public class SynchronizedBlockDemo {

    static int count;

    public static void main(String[] args) throws InterruptedException {
        SynchronizedBlockDemo synchronizedBlockDemo = new SynchronizedBlockDemo();

        Runnable runnable = () -> {
            synchronized (synchronizedBlockDemo) {
                for (int i = 0; i < 10000; i++) {
                    count++;
                }
            }
        };

        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

If a small part of the code needs to be synchronized, it may be worth using the synchronized code block to wrap the code that needs to be synchronized.

 

 

Underlying Semantic Principle of Synchronized


Synchronized Code Blocks

Analyze monitorenter and monitorexit.

 

- monitorenter

When a thread executes the monitorenter instruction, it attempts to acquire the ownership of the monitor corresponding to the objectref.

 

1. If the entry count of the monitor is 0, the thread enters the monitor and then sets the entry count to 1, making the thread the owner of the monitor.

 

2. If the thread already owns the monitor and is just re-entering, the entry count of the monitor is incremented by 1.

 

3. If another thread has occupied the monitor, the thread enters a blocked state until the entry count of the monitor is 0, and then retries to acquire the ownership of the monitor.

 

- monitorexit

The thread that executes monitorexit must be the owner of the monitor. When the instruction is executed, the enty count of the monitor is reduced by 1. If the entry count is 0 after the reduction, the thread exits the monitor and is no longer the owner of the monitor. If this monitor is blocked, other threads may attempt to take over its ownership.

 

 

Synchronization Methods

Not only monitorenter and monitorexit, but also there is an additional ACC_SYNCHRONIZED identifier in the pool of constants, which is basis for the synchornization of methods by the JVM.

 

When calling a method with the ACC_SYNCHRONIZED flag set, the executing thread needs to obtain the monitor lock before it can start executing the method, and then release the monitor lock after the method is executed. Whether the method returns normally or thrown an exception, the corresponding monitor lock will be released.

 

반응형