preface

The last article analyzed how to achieve the “lock” from scratch, although only the simplest lock, but the essence of the “lock” has been extracted, with these knowledge, this will analyze the system to provide the use and implementation of the lock -synchronized keyword. Through this article, you will learn:

1, how to use synchronized 2, synchronized source exploration 3, summary

1. How to use synchronized

Multithreaded access to critical sections

As you can see from the previous article, multithreaded access to critical sections requires locking:

A critical section can be a piece of code or a method.

Synchronized various usage modes

According to the lock function area, it can be divided into two categories:

Modification methods

There are two further types of decorators: instance methods and static methods. Let’s start with instance methods: instance methods

Public class TestSynchronized {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(); Println ("a = "+ testSynchronized. GetA () +" in mainThread"); system.out.println ("a = "+ testSynchronized. } catch (Exception e) {}} private synchronized void func1() {// modify a++; } private int getA() { return a; }}Copy the code

The above two threads T1 and T2 need to modify the value of the shared variable A and call the object method func1() of TestSynchronized to increment. Func1 () is called 10,000 times per thread, and the thread stops running at the end of the loop. In theory, each thread increments the value of a 10000 times, which means that the final value of a should be: a==20000.

It can be seen that the result of multithreaded access is correct, indicating that the instance method modified by synchronized can correctly realize multithreaded concurrency.

Static methods Let’s look at static methods:

Public class TestSynchronized {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(); System.out.println("a = "+ getA() +" in mainThread"); } catch (Exception e) {}} private static synchronized void func1() {// modify a a++; } private static int getA() { return a; }}Copy the code

Instead of decorating the instance method, you just change a to static and func1() to static, and the final result is the same as the previous instance method. It shows that the static method modified by synchronized can correctly realize multithreading concurrency.

Modify code block

When synchronized modifies methods (static methods/instance methods), apply for the lock before entering the method and release the lock after exiting the method. If you have a method that performs a lot of operations and requires only a small section of concurrent access, you’re overqualified to use synchronized for that small critical section. Synchronized provides a way to modify a block of code for this purpose. By lock type, decorated code blocks also fall into two categories: acquiring object locks

Private static Object Object = new Object(); Private void func1() {private void func1() {int b = 1000; int c = 0; if (c < b) { c++; Synchronized (object) {a++; }}Copy the code

It can be seen that the func1 method has other operations, but it is insensitive to multi-threaded operations. Only the shared variable A requires mutually exclusive access, so only operation A needs to be synchronized. Synchronized (object) Obtains the lock of the instance object.

Let’s see how to use a class lock:

Private void func1() {private void func1() {int b = 1000; int c = 0; if (c < b) { c++; Synchronized (TestSynchronized. Class) {a++; }}Copy the code

Instead of instantiating the object, use TestSynchronized. Class to obtain the TestSynchronized class lock.

summary

The above relationship is shown in a graph:

(Class lock is a Class lock) (Class lock is a Class lock) (Class lock is a Class lock)

Object lock

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

    private synchronized void func2() {
    }
    private void func3() {
    }
Copy the code

Func1 () and func2() both require an object lock (this refers to the object, the object on which the method is called), so access to them is mutually exclusive, while access to func3() is unaffected.

Kind of lock

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

    private static synchronized void func2() {
    }

    private static synchronized void func3() {
    }

    private static void func4() {
    }
Copy the code

Func1 (), func2(), and func3() all require a class lock, in this case a TestSynchronized. Class object, so access to them is mutually exclusive, while access to func4() is unaffected.

It follows that:

1. Class lock and object lock do not affect each other. 2

2. Preliminary study of synchronized source code

The above example is inseparable from the synchronized modifier, which is a keyword. How does the JVM recognize this keyword? Synchronized (synchronized)

Modify code block

First to see the Demo:

Public class TestSynchronized {// synchronized int a = 0; Object object = new Object(); public static void main(String args[]) { } private void add() { synchronized (object) { a++; }}}Copy the code

This is the code block decorated with object locks. Now compile it as a.class file, navigate to the TestSynchronized. Java file directory, open the command line, and type the following command:

javac TestSynchronized.java

TestSynchronized. Class will be generated in the same directory as the TestSynchronized. Java file. The.class file is invisible to the naked eye, so decompile it and still use the following command in the same directory:

javap -verbose -p TestSynchronized.class

The command line then outputs a string of results, of course, if you feel uncomfortable to view, you can put the output in a file, using the following command:

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

Let’s look at the highlights of the output:

The figure above highlights two instructions: Monitorenter and Monitorexit.

  • Monitorenter obtains locks
  • Monitorexit: Releases locks
  • The operation between the two is the locked critical region

Monitorexit has two, the latter being executed when an exception occurs

Code for monitorenter/ Monitorexit directive

Where is the code for the Monitorenter/Monitorexit directive? There are different explanations on the Internet, I prefer: github.com/farmerjohng… Analysis made by:

  • Only the template interpreter is used in Hotspot (templatetable_x86_64.cpp)

, the bytecodeInterpreter (bytecodeinterpreter.cpp) is useless

  • The template interpreter is assembly code, bytecode interpreter is implemented in C++, the logic of the two is almost the same, in order to make it easier to read bytecode interpreter for example

Code for monitorenter directive:

In bytecodeInterpreter. Cpp# 1804 rows.

Monitorexit directive: In bytecodeInterpreter. Cpp# 1911 rows.

The monitorenter/ Monitorexit directive is implemented in the monitorenter/ Monitorexit directive.

Modification methods

Let’s start with Demo:

Public class TestSynchronized {// synchronized int a = 0; Object object = new Object(); public static void main(String args[]) { } private synchronized void add() { a++; }}Copy the code

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

Unlike the modifiers, there is no Monitorenter/Monitorexit directive, but the ACC_SYNCHRONIZED tag. How is this interpreted? Take a look at the lock entry and exit corresponding to the code:

Method lock entry

In bytecodeInterpreter. Cpp# 643 rows. The red part on the icon can be seen from the name to determine whether the method is synchronous method, if synchronous method, then the step to obtain the lock. Look for the is_synchronized() function, in method.hpp.

Continue with accessFlags.hpp:

Eventually the JVM. H

As can be seen:

After modifying the synchronized keyword, the decompilated code contains: The ACC_SYNCHRONIZED flag corresponds to JVM_ACC_SYNCHRONIZED in the JVM, and the final use of this parameter is the is_synchronized() function to determine whether it is a synchronized method.

Method lock outlet

After the method is finished, run this code, which determines whether it is a synchronous method, and then release the lock and other operations.

3, summarize

Synchronized modifies code blocks and methods:

Monitorenter and MonitoreXIT instructions will be added before and after the critical section when the code block is modified. ACC_SYNCHRONIZED will be checked if the ACC_SYNCHRONIZED flag exists when the method is modified/exits the method Or ACC_SYNCHRONIZED, which is ultimately all about manipulating objects and acquiring locks.

After understanding the use of synchronized and its source code entry, the working mechanism of synchronized will be explored in depth. The next part will analyze the implementation mechanism of no-lock, biased lock, lightweight lock and heavyweight lock.

This article is based on JDK8.

If you like, please like, pay attention to your encouragement is my motivation to move forward

Continue to update, with me step by step system, in-depth study of Java/Android

More dry goods, public search [small fish love programming]