SoFunction
Updated on 2025-05-16

Detailed explanation of java synchronized locking and release process

Why need to add a lock

In a multi-threaded environment, when multiple threads run the same method at the same time, if there is a certain resource in it, there may be problems of successive operations, which will cause logic inconsistent, and we want the results of the program running from time to time.

How to add locks to threads

Here we only talk about synchronized locking, and only explain the principle of use, and other locking methods use another space.

Locking is to avoid the possibility of data inconsistency when multiple threads perform logical processing at the same time, which affects the accuracy of the program's logic.So we can use an object to set a lock status mark for the object. When other threads need to perform logical processing, they need to set the status successfully before they can proceed normally, otherwise they will block and hang.Here is the problem. If we directly add a status flag to the code, then there may still be cases where we set this state with multiple threads.

Here we can rely on the synchronized keyword provided by java.

Java memory layout and monitor lock

We just mentioned that we can set a lock status mark for the object. In fact, vjm has helped us realize it. After the bytecode is compiled, the java object we usually write will add an extra information to the memory. This involves another concept, the memory layout of the java object or the data structure of the java object.

When we create a new object through the new keyword, jvm will open a piece of memory in the heap memory to store the object instance. In addition to having some attribute methods that we define ourselves, the object instance will also have additional information.

It is divided into three parts:

  • Object header

The object header will store hashcode, GC information, lock tags, etc.

  • Instance data

Instance data is the field and method information we customize.

  • Fill Alignment

It is simply understood that storing an object in a virtual machine is an integer multiple of the size of 8 bytes, so if it is not enough, it will take up a little extra space to make up the number.

Okay, it's OK to just say this. Here you can see that the object has lock marks during actual operation. This is called monitor lock. In fact, the lock information on the object header will be more. Here I will summarize it briefly. If the synchronize keyword is used in the program, jvm will help us mark that the object is occupied by that thread and ensure that other threads will no longer own it. Only when the thread releases the lock of the modified object can the lock competition be re-entered.

At the same time, the synchorize keyword can ensure that the object operated is directly obtained from memory (memory visibility).

How to use it is as follows:

public class ThreadTest {

    public static void main(String[] args) {
        Task task = new Task();

        for (int i = 0; i< 50; i++) {
            new Thread(task).run();
        }

        (());
    }
}

class Task implements Runnable{
    private int count;

    private Object lock = new Object();

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
         = count;
    }

    @Override
    public void run() {
        int total = 0;
        while (++total <= 10000) {
            synchronized (lock) {
                count++;
            }
        }
    }
}

Who exactly has synchronized locked

The syntax rule for synchronized keywords is defined in a code block or when defining a method.

We just mentioned that there is a lock mark in the header of the java object, so the following logic is to compete for locking the object.

while (++total <= 10000) {
    synchronized (lock) {
        count++;
    }
}

If synchronized is defined in the method, it is a lock competition for the instance of the current class. Here is the instance object of C1, that is, C1 c1 = new C1(); and if there is a definition of C1 c11 = new C1() in the program, then it competes separately. That is, only the same object engages in lock competition.

class C1{
    private int count;

    public synchronized void run() {
        int total = 0;
        while (++total <= 10000) {
           count++;
        }
    }
}

If the object's method is static, then the class object that competes for lock is the class object, which is generated when jvm performs class bytecode loading.

class C1{
    private int count;

    public static synchronized void run() {
        int total = 0;
        while (++total <= 10000) {
           count++;
        }
    }
}

At this point, we can sort out the monitor lock and synchronized keywords. The above key information is: Java object memory layout, monitor lock, and processing logic of synchronized keywords. If you need to go deeper, you can study each point down.

Waiting and wake up of threads

wait() method

  • First of all, we need to understand the inheritance system of the wait() method. It is the base class method of the Object object, that is, all objects have the wait() method. After a thread calls the wait() method of java, the current thread will be blocked and suspended. The call here refers to the wait() method of the locked object being called in the thread.
  • The thread needs to wake up after being blocked and suspended. The wake-up method will be discussed below, but the overloaded method wait(long timeout) can also be called to make the thread automatically wake up after being blocked for a certain period of time.

notify() method

  • The notify() method is also inherited from an Object object. When a thread calls the notify method of the locked object, it will wake up the thread that failed to get the monitor lock when the object was used to obtain the monitor lock. If multiple threads are blocked at the same time, only one thread will be awakened by the notify() method. If all need to wake up, you can call the notifyAll() method.

So during the interview, you will be asked about the role of wait and notify. The knowledge points that can be focused on are:

  • 1. The lock must be obtained before calling wait, so it must be guaranteed to be in the synchronized block.
  • 2. After calling wait, the lock of the object will be released.
  • 3. Calling the notify() method must also acquire the lock, and it must also be guaranteed to be in the synchronized block.
  • 4. Call the notify() method to wake up a thread, and call the notifyAll() method to wake up all blocked threads.
  • 5. Calling notify() or notifyAll() methods only wakes up other blocked threads. They have the conditions to recompete the lock, but the current thread has not released the lock, and the lock will be released only after calling the wait() method.

Use oneProducer Consumer ModelLet’s take a look at the usage of wait and notify. The producer consumer model can be simply understood as having a container. When there is no data inside, the producer will add data to it. When it is full, the current work will be suspended and the consumer will wait for the consumer to consume data and notify him to continue adding.

Consumers will take data inside, and if there is no data, they will suspend their work and wait for the producer to produce the data and notify him to continue to consume.

public static void main(String[] args) {
        Object lock = new Object();
        AtomicInteger counter = new AtomicInteger(0);
        Queue&lt;Integer&gt; queue = new LinkedList&lt;&gt;();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    while (true) {
                        //If the queue has no data, call the wait() method to block yourself                        if (()) {
                            try {
                                ("Consumer thread blocking");
                                ();
                            } catch (InterruptedException e) {
                                ();
                            }
                        }

                        //If the queue is not empty, consume data; if the thread is awakened by the producer through the notifyAll() method, the thread reacquisitions the lock, which is executed from here.                        ("Consumer thread consumption data: " + ());
                        //After consumer consumption, wake up may actively block its producer because the queue was full before, and the previous queue was full.                        ();

                    }

                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    while (true) {
                        //If the queue data is full, call the wait() method to block yourself                        if (() &gt; 10) {
                            ("Producer thread blocking");
                            try {
                                ();
                            } catch (InterruptedException e) {
                                ();
                            }
                        }

                        //If the queue is not full, production data; if it is woken up by other threads, production data will be produced the next time the lock is obtained.                        ("Producer thread production data");
                        (());

                        //The queue has data, and there may be no data before awakening. You should take the initiative to do the ancestral temple, your own consumers                        ();
                    }

                }
            }
        }).start();

    }

Summarize

The above is personal experience. I hope you can give you a reference and I hope you can support me more.