UPD. Выложил код AS IS, он на все 100% не рабочий, т.к. есть краевые кейсы, которые не были побеждены. К ним относится synchronized внутри реализации ConcurrentHashMap, возможность изменения hashCode у синхронизуемых объектов, сложности с загрузкой длинных иерархий классов/интерфейсов. Да и вообще всё это депрекейтится с выходом JEP 491 в Java 24.
¯\_(ツ)_/¯
Java-Agent при запуске приложения начинает сканировать все загружаемые классы. Если находит файл с хотя бы одним synchronized методом или блоком, модифицирует его, используя ASM, иначе класс загружается в неизменном виде.
- С
synchronizedметода снимается флагACC_SYNCHRONIZED(перестаёт бытьsynchronized), всё его тело подразумевается как будто находится в блокеsynchronized (this). - В начале метода создаётся новая переменная, ссылающаяся на новую
ArrayDeque<Lock>. - Блоки
synchronizedзаменяются на вызов внешнего статического методаSharedReentrantLock::lock, возвращающегоLock(с аргументом = объектом, указанным в блоке). Лок возвращается уже взятым! - Результат вызова складывается в
ArrayDeque<Lock>. - Сам блок удаляется (если он был), заменяется на try-finally, где в блоке finally производится
ArrayDeque::popи вызовunlockна вернувшемся объекте. SharedReentrantLock::lockпри взятии помещается в статичнуюConcurrentHashMap<Object, ReentrantLock>, чтобы синхронизация на одном и том же объекте возвращала один и тот же лок.- Во время вызова
unlockперед фактическим освобождением лок пытается удалить себя изMap<Object, ReentrantLock>, если после освобожденияholdCountбудет равен нулю.