Java Synchronized Application and Source for Mutual Exclusion

Preface

With this knowledge, this article will analyze the use and implementation of the lock-synchronized keyword provided by the system.
In this article, you will learn:

1. How synchronized is used
2. Preliminary Study on synchronized Source Code
3. Summary

1. How synchronized is used

Multithreaded Access Critical Zone

As you can see from the previous article, multithreaded access to critical zones requires locks:

A critical zone can be either a piece of code or a method.

Various ways of using synchronized

They can be divided into two categories by locking area:

Modification Method

Modifiers can be divided into two categories: instance method and static method. First, look at the example method:
Instance Method

public class TestSynchronized {

    //Shared variable
    private int a = 0;

    public static void main(String args[]) {

        final TestSynchronized testSynchronized = new TestSynchronized();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                int count = 0;
                while (count < 10000) {
                    testSynchronized.func1();
                    count++;
                }

                System.out.println("a = " + testSynchronized.getA() + " in thread1");
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                int count = 0;
                while (count < 10000) {
                    testSynchronized.func1();
                    count++;
                }
                System.out.println("a = " + testSynchronized.getA() + " in thread2");
            }
        });
        t2.start();

        try {
            t1.join();
            t2.join();
            //Wait for t1,t2 to finish before printing results
            System.out.println("a = " + testSynchronized.getA() + " in mainThread");
        } catch (Exception e) {

        }
    }

    private synchronized void func1() {
        //Modify a
        a++;
    }

    private int getA() {
        return a;
    }
}

Both threads, t1 and t2, need to modify the value of shared variable a while calling the object method of TestSynchronized: func1() to increase itself. Each thread calls func1()10,000 times, the thread stops running after the loop ends. Theoretically, each thread adds 10,000 times to the value of a, that is, the value of the last a should be: a==20,000, to see the final value of printing a in the main thread:

It can be seen that the results of multithreaded access are correct, indicating that the synchronized modified instance method can correctly achieve multithreaded concurrency.

Static method
Let's look at the static method again:

public class TestSynchronized {

    //Shared variable
    private static int a = 0;

    public static void main(String args[]) {

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                int count = 0;
                while (count < 10000) {
                    func1();
                    count++;
                }

                System.out.println("a = " + getA() + " in thread1");
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                int count = 0;
                while (count < 10000) {
                    func1();
                    count++;
                }
                System.out.println("a = " + getA() + " in thread2");
            }
        });
        t2.start();

        try {
            t1.join();
            t2.join();
            //Wait for t1,t2 to finish before printing results
            System.out.println("a = " + getA() + " in mainThread");
        } catch (Exception e) {

        }
    }

    private static synchronized void func1() {
        //Modify a
        a++;
    }

    private static int getA() {
        return a;
    }
}

Relative to the Modify Instance method, you just changed a to the static type and changed func1() to the static method, resulting in the same results as the previous instance method.
Explains that the synchronized decorated static method correctly implements multithreaded concurrency.

Modify Code Block

Synchronized modifier (static method/instance method)If there is a method that performs more operations and requires concurrent access to only a small section, it will be trivial to decorate the method with synchronized for this small critical section. This provides a way to decorate a block of code.
Modifier code blocks are also divided into two categories by lock type:
Get Object Lock

    //Declare Lock Object
    private static Object object = new Object();
    private void func1() {
        //Areas that do not require mutually exclusive access
        int b = 1000;
        int c = 0;
        if (c < b) {
            c++;
        }

        //Modify a
       //Areas requiring mutually exclusive access
        synchronized (object) {
            a++;
        }
    }

You can see that although the func1 method has other operations, it is insensitive to multithreaded operations, and only the shared variable a requires mutually exclusive access, so only the synchronized modification of operation a is required.
synchronized (object) denotes the acquisition of a lock on an instance object: an object.

Get Class Lock
Let's see how class locks are used:

    private void func1() {
        //Areas that do not require mutually exclusive access
        int b = 1000;
        int c = 0;
        if (c < b) {
            c++;
        }

        //Modify a
        //Areas requiring mutually exclusive access
        synchronized (TestSynchronized.class) {
            a++;
        }
    }

Instead of instantiating the object this time, you use TestSynchronized.class directly, which means you get a TestSynchronized class lock.

Summary

Graph these relationships:

1. Both modifiers and code blocks ultimately acquire object locks (class locks are Class object locks)
2. Instance method and object lock acquire the same lock (common object lock)
3. Static methods and Class locks acquire the same lock (Class lock-Class object lock)

Object Lock

    private void func1() {
        synchronized (this) { }
    }

    private synchronized void func2() {
    }
    private void func3() {
    }

Both func1() and func2() need to acquire an object lock (this refers to the object itself, which calls the method), so access to both is mutually exclusive and access to func3() is unaffected.

Class Lock

    private void func1() {
        synchronized (TestSynchronized.class) { }
    }

    private static synchronized void func2() {
    }

    private static synchronized void func3() {
    }

    private static void func4() {
    }

func1(), func2(), and func3() all need to acquire a class lock, where the class lock is a TestSynchronized.class object, so access to the three is mutually exclusive and access to func4() is unaffected.

From this we can see that:

1. Class locks and object locks do not affect each other
2. Multithreads need to acquire the same lock to be mutually exclusive

2. Preliminary Study on synchronized Source Code

The above example cannot be separated from the synchronized modifier, which is a keyword. How does the JVM recognize this keyword? First, look at the results of the synchronized compilation:

Modify Code Block

First, Demo:

public class TestSynchronized {

    //Shared variable
    int a = 0;

    Object object = new Object();

    public static void main(String args[]) {
    }

    private void add() {
        synchronized (object) {
            a++;
        }
    }
}

Now compile it as a.class file, locate it in the TestSynchronized.java file directory, open the command line, and type the following command:

javac TestSynchronized.java

TestSynchronized.class is generated in the same directory as the TestSynchronized.java file.
The.class file is invisible to the naked eye, so decompile it and use the following commands in the same directory:

javap -verbose -p TestSynchronized.class

Then the command line outputs a list of results. Of course, if you find it inconvenient to view, you can put the output in a file using the following commands:

javap -verbose -p TestSynchronized.class > mytest.txt

Take a look at the output highlights:

The figure above highlights two directives: monitorenter and monitorexit.

  • monitorenter indicates acquisition of a lock
  • monitorexit means release lock
  • The operation between the two is the locked critical zone
    There are two monitorexit s, the latter being executed when an exception occurs

monitorenter/monitorexit directive corresponding code

Where is the code for the monitorenter/monitorexit directive?
There are different explanations online, and I prefer:https://github.com/farmerjohngit/myblog/issues/13 Analysis done in:

  • Only Template Interpreter (templateTable_x86_64.cpp) is used in Hotspot
    , the bytecodeInterpreter.cpp is not used at all
  • Template interpreters are assembled code, and byte code interpreters are implemented in C++. Their logic is different. For easier reading, take byte code interpreter as an example

The monitorenter directive corresponds to the code:

In line bytecodeInterpreter.cpp#1804.

The monitorexit directive corresponds to the code:

In line bytecodeInterpreter.cpp#1911.

From the above, we found the code entry corresponding to the monitorenter/monitorexit directive, which is the specific implementation location of the directive.

Modification Method

Let's start with Demo:

public class TestSynchronized {

    //Shared variable
    int a = 0;

    Object object = new Object();

    public static void main(String args[]) {
    }

    private synchronized void add() {
        a++;
    }
}

Using the same javap directive, the result is as follows:

Unlike the modifier block, there is no monitorenter/monitorexit directive, but there is an additional ACC_SYNCHRONIZED tag. How does this tag resolve?
First look at the code for the entrance and exit of the lock:
Method Lock Entry

In line bytecodeInterpreter.cpp#643.
The red part of the icon above tells you by name whether the method is synchronous or, if it is, the steps to get the lock.
Find the is_synchronized() function in method.hpp.

Keep looking at accessFlags.hpp:

Final look at jvm.h

You can see that:

After using the synchronized keyword modifier, the decompiled code contains the following tags: ACC_SYNCHRONIZED corresponds to JVM_ACC_SYNCHRONIZED in the JVM, and this parameter is ultimately used to determine whether it is a synchronized method through the is_synchronized() function.

Method Lock Exit

Run this code at the end of the method to determine if it is a synchronization method and release the lock.

3. Summary

synchronized modifies code blocks and methods, which are similar and different:

1. When a code block is decorated, the monitorenter and monitorexit directives are added before and after the critical zone when compiled
2. When entering/exiting the modifier method, the existence of the ACC_SYNCHRONIZED tag will be determined
3. Whether you use monitorenter/monitorexit or ACC_SYNCHRONIZED, you end up writing on the object header and need to acquire locks.

Understanding the use of synchronized and its source entry, the next section will explore its working mechanism. The next section will analyze the implementation mechanisms of unlock, bias lock, lightweight lock, and heavy lock.

This article is based on jdk8.

If you like it, please pay attention to it. Your encouragement is my driving force

Continuous updates, step by step with me to learn more about Java/Android

Tags: Java

Posted on Sun, 12 Sep 2021 22:27:31 -0400 by codeDV