Глубокое понимание синхронизированного ключевого слова

Java
Глубокое понимание синхронизированного ключевого слова

Глубокое понимание синхронизированного ключевого слова

Synchronized — один из важных инструментов параллельного программирования, мы должны научиться использовать и освоить его принципы.

Концепция и роль

Ключевые слова, поставляемые с JVM, можно использовать в бизнес-сценариях, требующих безопасности потоков для обеспечения безопасности потоков.

использование

По назначению замок можно разделить наЗамок объектаиблокировка классаПо положению в коде его можно разделить наформа методаиформа блока кода

Блокировка объекта

Объект блокировки является текущимthisИли скорееТекущий классэкземпляр объекта

public void synchronized method() {
    System.out.println("我是普通方法形式的对象锁");
}

public void method() {
    synchronized(this) {
        System.out.println("我是代码块形式的对象锁");
    }
}

блокировка класса

Замок — это текущий класс или объект класса указанного класса. Класс может иметь несколько объектов-экземпляров, но может иметь только один объект класса.

public static void synchronized method() {
    System.out.println("我是静态方法形式的类锁");
}

public void method() {
    synchronized(*.class) {
        System.out.println("我是代码块形式的类锁");
    }
}

SimpleExample

Обратитесь к MOOC «Душа Java High Concurrency: синхронизированный глубокий анализ»

Самое основное использование было указано в псевдокоде в предыдущем использовании заголовка, вот несколько немного измененных вариантов использования, основанных на приведенном выше. 1. Несколько экземпляров, блокировка текущего экземпляра, выполнение синхронно, блокировка текущего объекта класса, выполнение асинхронно

public class SimpleExample implements Runnable {
    static SimpleExample instance1 = new SimpleExample();
    static SimpleExample instance2 = new SimpleExample();
    
    @Override
    public void run() {
        method1();
        method2();
        method3();
        method4();
    }
    
    public synchronized void method1() {
        common();
    }
    
    public static synchronized void method2() {
       commonStatic();
    }
    
    public void method3() {
        synchronized(this) {
            common();
        }
    }

    public void method4() {
        synchronized(MultiInstance.class) {
            common();
        }
    }
    
    public void method5() {
        common();
    }
    
    public void method6() {
        commonStatic();
    }
    
    public void common() {
        System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行");
        try {
            Thread.sleep(1000);
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程 " + Thread.currentThread().getName() + " 执行完毕");
    }
    
    public static void commonStatic() {
        System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行");
        try {
            Thread.sleep(1000);
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程 " + Thread.currentThread().getName() + " 执行完毕");
    }
    
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance2);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("finished");
    }
}

method1()、method3()结果为:
    线程 Thread-0 正在执行
    线程 Thread-1 正在执行
    线程 Thread-0 执行完毕
    线程 Thread-1 执行完毕
    finished
    
method2()、method4()执行结果为:
    线程 Thread-0 正在执行
    线程 Thread-0 执行完毕
    线程 Thread-1 正在执行
    线程 Thread-1 执行完毕
    finished

2. Блокировки объектов и блокировки классов, объекты блокировок разные и не влияют друг на друга, поэтому выполняются асинхронно

// 将run方法改为
@Override
public void run() {
    if("Thread-0".equals(Thread.currentThread().getName())) {
        method1();   
    } else {
        method2();
    }
}
// 将main方法改为
public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(instance1);
    Thread t2 = new Thread(instance1);
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println("finished");
}
结果为:
    线程 Thread-0 正在执行
    线程 Thread-1 正在执行
    线程 Thread-1 执行完毕
    线程 Thread-0 执行完毕
    finished

3. Блокировка объекта и обычные методы без блокировки, обычные методы не должны удерживать блокировки, поэтому асинхронное выполнение

// 将run方法改为
@Override
public void run() {
    if("Thread-0".equals(Thread.currentThread().getName())) {
        method1();   
    } else {
        method5();
    }
}
// main方法同 2
结果为:
    线程 Thread-0 正在执行
    线程 Thread-1 正在执行
    线程 Thread-0 执行完毕
    线程 Thread-1 执行完毕
    finished

4. Блокировка класса и статические методы без блокировки, асинхронное выполнение

// 将run方法改为
@Override
public void run() {
    if("Thread-0".equals(Thread.currentThread().getName())) {
        method1();   
    } else {
        method6();
    }
}
// main方法同 2
结果为:
    线程 Thread-0 正在执行
    线程 Thread-1 正在执行
    线程 Thread-0 执行完毕
    线程 Thread-1 执行完毕
    finished

5. Метод выдает исключение, и синхронизированная блокировка автоматически снимается.

// run方法改为
@Override
public void run() {
    if ("Thread-0".equals(Thread.currentThread().getName())) {
        method7();
    } else {
        method8();
    }
}

public synchronized void method7() {
    ...
    throw new RuntimeException();
}

public synchronized void method8() {
    common();
}

public static void main(String[] args) throws InterruptedException {
    // 同 2
}

结果为:
    我是抛出异常的加锁方法,我叫 thread---------1
    我是没有异常的加锁方法,我叫 thread---------2
    Exception in thread "thread---------1" java.lang.RuntimeException
	    at com.marksman.theory2practicehighconcurrency.synchronizedtest.SynchronizedException9.method1(SynchronizedException9.java:29)
	    at com.marksman.theory2practicehighconcurrency.synchronizedtest.SynchronizedException9.run(SynchronizedException9.java:15)
	    at java.lang.Thread.run(Thread.java:748)
    thread---------2 运行结束
    finished
// 这说明抛出异常后持有对象锁的method7()方法释放了锁,这样method8()才能获取到锁并执行。

6. Реентерабельные функции

public class SynchronizedRecursion {
    int a = 0;
    int b = 0;
    private void method1() {
        System.out.println("method1正在执行,a = " + a);
        if (a == 0) {
            a ++;
            method1();
        }
        System.out.println("method1执行结束,a = " + a);
    }
    
    private synchronized void method2() {
        System.out.println("method2正在执行,b = " + b);
        if (b == 0) {
            b ++;
            method2();
        }
        System.out.println("method2执行结束,b = " + b);
    }

    public static void main(String[] args) {
        SynchronizedRecursion synchronizedRecursion = new SynchronizedRecursion();
        synchronizedRecursion.method1();
        synchronizedRecursion.method2();
    }
}
结果为:
    method1正在执行,a = 0
    method1正在执行,a = 1
    method1执行结束,a = 1
    method1执行结束,a = 1
    
    method2正在执行,b = 0
    method2正在执行,b = 1
    method2执行结束,b = 1
    method2执行结束,b = 1
    
// 可以看到method1()与method2()的执行结果一样的,method2()在获取到对象锁以后,在递归调用时不需要等上一次调用先释放后再获取,而是直接进入,这说明了synchronized的可重入性.
// 当然,除了递归调用,调用同类的其它同步方法,调用父类同步方法,都是可重入的,前提是同一对象去调用,这里就不一一列举了.

в заключении

  • Блокировка может быть получена только одним потоком одновременно, и поток, который не получает блокировку, должен ждать;
  • У каждого экземпляра своя блокировка, и разные экземпляры не влияют друг на друга;
  • Когда объект блокировки представляет собой статический метод, модифицированный *.class и синхронизированный, все объекты совместно используют блокировку класса;
  • Будь то метод, он снимает блокировку или метод выбрасывается.
  • Методы, украшенные синхронизацией, являются реентерабельными.

Принцип реализации синхронизированного

мониторэнтер и мониторэксит

Скомпилируйте следующие две части кода в файлы .class с javac *.java соответственно, а затем декомпилируйте файлы javap -verbose *.class.

public class SynchronizedThis {
	public void method() {
		synchronized(this) {}
	}
}

// 反编译结果
public void method();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: aload_1
         5: monitorexit
         6: goto          14
         9: astore_2
        10: aload_1
        11: monitorexit
        12: aload_2
        13: athrow
        14: return
public class SynchronizedMethod {
	public synchronized void method() {}
}

// 反编译结果
public synchronized void method();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 2: 0

можно увидеть:

  • Synchronized добавляется в блок кода, и JVM передается черезmonitorenterиmonitorexitКонтролировать получение и снятие замков;
  • Synchronized добавляется в метод, и JVM передается черезACC_SYNCHRONIZEDФлаги контролируются, но также существенно контролируются директивами мониторинга и Monitorexit.

заголовок объекта

См. "Подробное объяснение заголовка объекта Java"

Выше мы упомянули Монитор, что это за призрак? Фактически объект хранится в памяти, в том числезаголовок объекта,данные экземпляраиВыравнивание прокладки, где заголовок объекта включает Отметьте Word и введите указатели.

Mark Word

Mark Word используется для хранения данных времени выполнения самого объекта, таких как хэш-код (identity_hashcode), возраст генерации GC (возраст), флаг состояния блокировки (блокировка), блокировки, удерживаемые потоками, смещенный идентификатор потока (поток), смещенное время Штамп (эпоха) и т. д., размер занимаемой памяти соответствует разрядности виртуальной машины.

Mark Word (32 bits) Состояние блокировки состояния
identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 Нормально без блокировки
thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 Смещенный смещенный замок
ptr_to_lock_record:30 | lock:2 Легкий заблокированный
ptr_to_heavyweight_monitor:30 | lock:2 Тяжеловес закрыт
| lock:2 Отмечен для GC GC Tag
Mark Word (64 bits) Состояние блокировки состояния
unused:25|identity_hashcode:31|unused:1|age:4|biased_lock:1|lock:2 Нормально без блокировки
thread:54 |epoch:2|unused:1|age:4|biased_lock:1|lock:2 Смещенный смещенный замок
ptr_to_lock_record:62 | lock:2 Легкий заблокированный
ptr_to_heavyweight_monitor:62 | lock:2 Тяжеловес закрыт
| lock:2 Отмечен для GC Знак GC

Как видите, монитор существует в Mark Word.

указатель типа

Введите указатель на метаданные класса объектаmetadata, виртуальная машина использует этот указатель, чтобы определить, экземпляром какого класса является объект.

Статус блокировки

biased_lock lock государство
0 01 нет замка
1 01 Блокировка смещения
0 00 Легкий замок
0 10 тяжелый замок
0 11 маркеры ГХ

Оптимизация JDK для синхронизированных

Перед JDK1.6 синхронизация была очень тяжелой, поэтому она не была одобрена разработчикам. С оптимизацией синхронизации в последующих версиях JDK, чтобы сделать ее все более легким, все еще очень просты в использовании. Даже concurrenthashmap используется в Положите метод JDK. В JDK1.8 синхронизация была изменена с Reetrantlock.trylock (), чтобы синхронизировать. А также представил концепцию блокировки смещения, легкий замок и т. Д. Ниже приведен процесс приобретения блокировки смещения и легкой блокировки

Woohoo.process on.com/diagram ing/…

Обратитесь к «Открытому классу Академии Гупао».Код извлечения: S6VX

Предвзятый замок Baied_lock.

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

Разница между синхронизированным и реинцем

Справочное время гиков «Ядро технологии Java говорит, что 36 столбцов»

разница synchronized ReentrantLock
гибкость Простой код, автоматическое получение и освобождение блокировок Относительно громоздкий, нужно вручную доставать, снимать блокировку
Это реентерабельный да да
Действующая позиция Работает с методами и блоками кода Может использоваться только в кодовых блоках
Как получить и разблокировать блокировки мониторэнтер, мониторэкзит, ACC_SYNCHRONIZED Попробуйте неблокирующую блокировку захвата tryLock(), блокировку захвата по тайм-ауту tryLock(длительный тайм-аут, блок TimeUnit), разблокировку()
результат получения замка понятия не имею Как видите, tryLock() возвращает логическое значение.
Меры предосторожности при использовании 1. Объект блокировки не может быть пустым (блокировка хранится в заголовке объекта, нуль не имеет заголовка объекта)
2. Объем не должен быть слишком большим
1. Не забудьте разблокировать() в finally, иначе возникнет взаимоблокировка.
2, не получайте блокировку процесса записи в блоке TRY, потому что если произойдет исключение при получении блокировки, исключение брошено, он также приведет к выпуску блокировки без причины.