The first lock of Java people -- synchronized

Posted by Stephen on Mon, 24 Jan 2022 11:30:37 +0100

Wedge

In the last article, we learned the volatile keyword. In this article, we continue to learn another keyword commonly used in concurrent programming - synchronized.

synchronized usage

First, let's look at several common ways to use synchronized.

Use synchronized(this) to decorate code blocks

class SyncThread implements Runnable {
    private static int count;
    public SyncThread() {
        count = 0;
    }
    public  void run() {
       synchronized (this){
            for (int i = 0; i < 50; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + (count++));
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        System.out.println("The same object, using synchronized keyword");
        SyncThread syncThread = new SyncThread();
        Thread thread1 = new Thread(syncThread, "Same object: thread");
        Thread thread2 = new Thread(syncThread, "Same object: thread 2");
        thread1.start();
        thread2.start();
    }
}

results of enforcement

Same object: thread:0
......
Same object: thread 2:99

Use synchronized to modify common methods

We modify the SyncThread class and add the synchronized keyword to the run method

class SyncThread implements Runnable {
    private static int count;
    public SyncThread() {
        count = 0;
    }
    public synchronized void run() {
        for (int i = 0; i < 50; i++) {
            try {
                System.out.println(Thread.currentThread().getName() + ":" + (count++));
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

results of enforcement

Same object: thread:0
......
Same object: thread 2:99

Modifying static methods with synchronized

We modify the SyncThread class, the extraction method is static, and add the synchronized keyword to the extracted method

class SyncThread implements Runnable {
    private static int count;
    public SyncThread() {
        count = 0;
    }
    public void run() {
        doSome();
    }
    private synchronized static void doSome() {
        for (int i = 0; i < 50; i++) {
            try {
                System.out.println(Thread.currentThread().getName() + ":" + (count++));
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

results of enforcement

Same object: thread:0
......
Same object: thread 2:99

Use synchronized(xxx.class) to decorate a class

class SyncThread implements Runnable {
    private static int count;
    public SyncThread() {
        count = 0;
    }
    public void run() {
        synchronized (SyncThread.class){
            for (int i = 0; i < 50; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + (count++));
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

results of enforcement

Same object: thread:0
......
Same object: thread 2:99

Summary

Through the above basic use, we can boldly guess that synchronized actually locks objects, whether instance objects or class objects, why does it lock objects? In fact, the underlying principle of synchronized is related to jvm instructions and monitor.

synchronized underlying principle

Each object has a monitor. If you use the synchronized keyword, there will be two instructions monitorenter and monitorexit in the underlying compiled jvm instructions.

There is a counter starting from 0 in monitor. If a thread wants to obtain the lock of the monitor, it needs to check whether the counter of the object is 0. If it is 0, it means that no one has obtained the lock, he can obtain the lock, and then add 1 to the counter; If the counter is 1, it indicates that the current lock has been occupied by others. At this time, he will wait for a certain time before trying to obtain the lock; If the scope of synchronized modification is out, there will be a monitorexit instruction, and the monitor counter of the object will be decremented by 1. The value mentions that the monitor is reentrant, that is, an object can be locked multiple times. When the lock is added several times, the counter will increase several times, and when the lock is released, it will decrease, and finally become 0.

Here is a simple example

public class SynchronizedDemo {
    public static void main(String[] args) {
        synchronized (SynchronizedDemo.class) {
        }
        method();
    }
 
    private static void method() {
    }
}

Compile the above files and execute javap - V synchronizeddemo. In the directory corresponding to the target Class, you can see the corresponding bytecode as follows:

wait and notify

First, let's look at the following simple examples

class ThreadA extends Thread{
    public ThreadA(String name) {
        super(name);
    }
    public void run() {
        synchronized (this) {
            try {
                //  Block the current front line for 1 s and ensure T1 of the main program wait();  Execute before executing notify()
                Thread.sleep(5000);
            } catch (Exception e) {
                e.printStackTrace();
            }           
            System.out.println("Wait 5 s end "+Thread.currentThread().getName()+" Awakened");
            // Wake up the current wait thread
            this.notify();
        }
    }
}

public class WaitTest {
    public static void main(String[] args) {
        ThreadA t1 = new ThreadA("t1");
        synchronized(t1) {
            try {
                // Start "thread t1"
                System.out.println(Thread.currentThread().getName()+" implement t1 thread ");
                t1.start();
                // The main thread waits for t1 to wake up via notify().
                System.out.println(Thread.currentThread().getName()+" Start waiting");
                //  Instead of making the t1 thread wait, the thread currently executing wait waits
                t1.wait();
                System.out.println(Thread.currentThread().getName()+" Wait for the end to continue");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

The result is:

main implement t1 thread 
main Start waiting
 Wait 5 s end t1 Awakened
main Wait for the end to continue

The underlying principles of wait and notify are also based on monitor. The underlying layer calls the wait and set methods respectively, so they must lock, wait and notify the same object. In this way, they can operate through the counters, wait and set related to monitor in an object.

reflection

Thinking one

Is the value of count 99 in the following example? Why?

public static void main(String[] args) throws InterruptedException {
    System.out.println("Different objects, using synchronized keyword");
    Thread thread3 = new Thread(new SyncThread(), "Different objects: Line 3");
    Thread thread4 = new Thread(new SyncThread(), "Different objects: thread four");
    thread3.start();
    thread4.start();
}

class SyncThread implements Runnable {
    private static int count;
    public SyncThread() {
        count = 0;
    }
    public void run() {
        synchronized (this){
            for (int i = 0; i < 50; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + (count++));
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.notify();
        }
    }
}

Thinking II

Is the value of count 99 in the following example? Why?

public static void main(String[] args) throws InterruptedException {
    System.out.println("Different objects, using synchronized keyword");
    Thread thread3 = new Thread(new SyncThread(), "Different objects: Line 3");
    Thread thread4 = new Thread(new SyncThread(), "Different objects: thread four");
    thread3.start();
    thread4.start();
}

class SyncThread implements Runnable {
    private static int count;
    public SyncThread() {
        count = 0;
    }
    public void run() {
        synchronized (SyncThread.class){
            for (int i = 0; i < 50; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + (count++));
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Topics: Java Back-end Concurrent Programming