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
будет равен нулю.