Synchronized minimalist introduction to "principles of properties"

Keywords: Java Back-end

Synchronized property

Reentrant

Reentrant refers to the outer function of the same thread. After obtaining the lock, the inner function can obtain it again. What people say is:

If a thread gets a lock, it can continue to use it once it gets the lock. After the outer function obtains the lock, the inner function can continue to use the lock. If it is non reentrant, once the lock is taken, if you want to continue to use it, you need to release it first and then compete again to obtain the lock.

advantage

Avoid deadlock and improve encapsulation

For example, if there are two synchronization methods, method 1 needs to call method 2. Assuming that a precondition is Synchronized and non reentrant, when method 1 gets a lock and starts execution, method 2 needs to be called, and method 2 needs to compete for a lock again, and both method 1 and method 2 are methods of the same class, that is, the required lock is the same. At this time, method 1 will need to obtain the lock of method 2. However, since method 1 does not complete the execution and does not throw an exception, the lock it holds will not be released. It will fall into the deadlock situation of locking and not releasing the lock.

Through the above example, after knowing the reentrancy, you can avoid constantly acquiring and releasing locks when writing relevant code, which simplifies the difficulty of development.

Code demonstration

public class DemoCode6 {
    int a = 0;

    public synchronized void method1() {
        System.out.println("This is method1, a= " + a);
        if (a == 0) {
            a++;
            method1();
        }
    }

    public static void main(String[] args) {
        DemoCode6 demoCode6 = new DemoCode6();
        demoCode6.method1();
    }
}

When it runs, it does not need to re acquire the lock to recurse, and the running result is

This is method1, a= 0
This is method1, a= 1

The same is true for this example. When calling method 1 and method 2, locks should be obtained, and method 1 does not need to obtain locks when calling method 2.

public class DemoCode6 {
    public synchronized void method1() {
        System.out.println("This is method1");
        method2();
    }

    public synchronized void method2() {
        System.out.println("This is method2");
    }

    public static void main(String[] args) {
        DemoCode6 demoCode6 = new DemoCode6();
        demoCode6.method1();
    }
}

This is method1
This is method2

Non interruptible

Once the lock has been acquired by other threads, if you still want to obtain it, you can only choose to wait or block until other threads release the lock. If other threads never release the lock, they can only wait forever.

Principle of locking and releasing lock

The timing of lock acquisition and locking can only be operated when entering and exiting (including throwing exceptions) synchronous code blocks

Equivalent code representation

For the equivalent code implementation of locking and releasing locks, you can refer to the Lock implementation of Java itself

public class DemoCode7 {
    Lock lock = new ReentrantLock();

    private void method() {
        lock.lock(); // Lock
        try {
            System.out.println("Something....");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock(); // Release lock
        }
    }
}

Bytecode represents monitor instruction

Write a class

public class DemoCode8 {
    private Object object = new Object();

    public void test(Thread thread) {
        synchronized (object) {
            
        }
    }
}

Try to view bytecode after compilation

The monitorenter in line 6 is actually the instruction to lock, and the monitorexit in lines 8 and 14 is the instruction to release the lock.

As for two instructions for releasing locks, Synchronized must ensure that the lock will be released. There are two situations for releasing locks, one is to exit and the other is to throw an exception. These two instructions are the manifestations of these two situations.

Reentrant principle

Lock count

  • The JVM records the number of times the lock was placed

  • When locking for the first time, the number of times changes from 0 to 1. After that, if locking again, it will continue to increase

  • When exiting a code block, the code block counter decreases by one every time it exits, and the lock is not really released until the counter is zero

visibility

Visibility is related to the Java memory model. This article only covers a small part related to Synchronized. You only need to understand:

  • The result of one thread execution is not necessarily visible to other threads

  • Thread 1 operates on a variable, and subsequent threads may not be able to read the operated value

  • Synchronized ensures visibility

The specific visibility content needs to be learned in combination with the memory model

Synchronized defects

  • Low efficiency: there are few lock releases, the timeout cannot be set when trying to obtain the lock, and the terminal cannot a process trying to obtain the lock

  • Not flexible enough: the timing of locking and releasing locks is single, and each lock has only a single condition, which can only be an object (compared with read-write locks, only write operations are locked)

Posted by gtanzer on Sun, 31 Oct 2021 01:52:56 -0700