Глубокое понимание многопоточности (1) - принцип реализации Synchronized

Java задняя часть JVM переводчик

synchronized, является очень важным ключевым словом в Java для разрешения доступа к синхронизации данных в параллельных ситуациях. Когда мы хотим гарантировать, что общий ресурс доступен только одному потоку за раз, мы можем использовать его в нашем коде.synchronizedКлючевое слово блокирует класс или объект. Итак, эта статья познакомитsynchronizedКаков принцип реализации ключевых слов. Перед прочтением этой статьи рекомендуется прочитатьКак виртуальная машина Java выполняет синхронизацию потоков.

декомпилировать

Как мы все знаем, в JavasynchronizedСуществует две формы использования: синхронизированные методы и синхронизированные блоки кода. код показывает, как показано ниже:

/**
 * @author Hollis 17/11/9.
 */
public class SynchronizedTest {

    public synchronized void doSth(){
        System.out.println("Hello World");
    }

    public void doSth1(){
        synchronized (SynchronizedTest.class){
            System.out.println("Hello World");
        }
    }
}

Давайте использоватьJavapПри декомпиляции приведенного выше кода получается следующее (некоторая бесполезная информация отфильтровывается):

  public synchronized void doSth();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Hello World
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return

  public void doSth1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #5                  // class com/hollis/SynchronizedTest
         2: dup
         3: astore_1
         4: monitorenter
         5: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         8: ldc           #3                  // String Hello World
        10: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        13: aload_1
        14: monitorexit
        15: goto          23
        18: astore_2
        19: aload_1
        20: monitorexit
        21: aload_2
        22: athrow
        23: return

декомпилироватьПосле этого мы можем увидеть байт-код, сгенерированный для нас компилятором Java. заdoSthа такжеdoSth1обрабатывается несколько иначе. То есть. JVM по-разному обрабатывает синхронизированные методы и синхронизированные блоки кода.

Для синхронизированных методов JVM используетACC_SYNCHRONIZEDмаркер для достижения синхронизации. Для синхронизированных кодовых блоков. JVM используетmonitorenter,monitorexitДве инструкции для достижения синхронизации.

Для этой части вСпецификация JVMСоответствующее описание также можно найти в .

Синхронный метод

The Java® Virtual Machine SpecificationВведение в синхронизацию на уровне методов:

Method-level synchronization is performed implicitly, as part of method invocation and return. A synchronized method is distinguished in the run-time constant pool's method_info structure by the ACC_SYNCHRONIZED flag, which is checked by the method invocation instructions. When invoking a method for which ACC_SYNCHRONIZED is set, the executing thread enters a monitor, invokes the method itself, and exits the monitor whether the method invocation completes normally or abruptly. During the time the executing thread owns the monitor, no other thread may enter it. If an exception is thrown during invocation of the synchronized method and the synchronized method does not handle the exception, the monitor for the method is automatically exited before the exception is rethrown out of the synchronized method.

Суть в том, что синхронизация на уровне метода является неявной. В постоянном пуле синхронизированного метода будет одинACC_SYNCHRONIZEDлоготип. Когда поток хочет получить доступ к методу, он проверяет, существует лиACC_SYNCHRONIZED, если он установлен, вам необходимо сначала получить блокировку монитора, затем начать выполнение метода и снять блокировку монитора после выполнения метода. В это время, если другие потоки запросят выполнение метода, они будут заблокированы, поскольку блокировка монитора не может быть получена. Стоит отметить, что если во время выполнения метода возникает исключение, и оно не обрабатывается внутри метода, блокировка монитора будет автоматически снята до того, как исключение будет выброшено за пределы метода.

Блок синхронизированного кода

Использование синхронизированного блока кодаmonitorenterа такжеmonitorexitРеализованы две инструкции.The Java® Virtual Machine SpecificationВведение в эти две команды:

monitorenter

Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:

If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.

If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.

If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.

monitorexit

The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.

The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

Общее содержание таково: Вы можете выполнитьmonitorenterИнструкция понимается как блокировка, выполнениеmonitorexitЭто понимается как снятие блокировки. Каждый объект поддерживает счетчик количества раз, когда он был заблокирован. Этот счетчик разблокированных объектов равен 0, когда поток получает блокировку (выполняетmonitorenter), счетчик увеличивается до 1, и когда тот же поток снова получает блокировку объекта, счетчик снова увеличивается. Когда тот же поток освобождает блокировку (выполняетсяmonitorexitинструкция), счетчик снова уменьшается. когда счетчик равен 0. Блокировка будет снята, и другие потоки смогут получить блокировку.

Суммировать

Синхронный метод черезACC_SYNCHRONIZEDКлючевое слово неявно блокирует метод. Когда метод, который должен быть выполнен потоком, помеченACC_SYNCHRONIZED, перед выполнением метода необходимо получить блокировку.

Блок синхронизированного кода черезmonitorenterа такжеmonitorexitВыполнить, чтобы заблокировать. Когда поток выполняется дляmonitorenterКогда блокировка получена, можно выполнить следующий метод. Когда поток выполняется дляmonitorexitКогда замок снят.

Каждый объект поддерживает счетчик количества раз, когда он был заблокирован.Если значение счетчика равно 0, это означает, что блокировка может быть получена любым потоком. Когда счетчик не равен 0, только поток, получивший блокировку, может снова получить блокировку. для повторного входа в замок.

Пока что у нас есть общее понимание принципа Synchronized. Но есть еще несколько вопросов, которые не были четко сформулированы, например, что такое Monitor? Где хранится состояние блокировки объекта? Не волнуйтесь, я представлю его позже.