Information
- Rating
- Does not participate
- Location
- Санкт-Петербург, Санкт-Петербург и область, Россия
- Date of birth
- Registered
- Activity
Specialization
Backend Developer, Database Developer
Lead
Java
High-loaded systems
Designing application architecture
Database design
Multiple thread
Code Optimization
SOA
REST
SQL
NoSQL
Во первых вы не правильно оцениваете жизненный цикл. В любом коде мы можем рассчитывать, что метод внёс какие-то изменения только по выходу из метода, а не на основании входа в метод. Если исходить из правильных предпосылок, то после выхода пишущего потока из метода setValue, все другие потоки будут иметь актуальные данные.
Во вторых, если вы, говоря Immutable, подразумеваете unmodifableMap, то в статье было написано, что это избыточная конструкция. Как вы правильно заметили, parameters имеет область видимости только внутри класса. Если класс написан правильно, то нет нужды закрывать его враппером с защитой от изменений. Однако, сам паттерн Immutable здесь нужен, чтобы обеспечить чтение без блокировки.
В третьих, если вы добавите блокировку на чтение, то всё, что написано в статье вообще не нужно, так как принцип happends before будет обеспечен ключевым словом synchronized. Но это будет уже не lock-free алгоритм, а банальная блокировка по монитору объекта и читающие потоки будут выстраиваться друг за другом для занятия этой блокировки.
Вы невнимательно читали статью. Здесь synchronized не защищает доступ к переменной parameters, а является критической секцией на время пересборки Immutable-объекта. Это нужно для того, чтобы разные потоки не смогли это сделать одноврменно.
Это всё так, но, как говорится, есть нюанс. А именно, любая синхронизация данных между потоками в Java делает всё тоже самое, чтобы соблюдался принцип
happens-before
.Даже если взять самый быстрый lock в JDK - ReentrantReadWriteLock.ReadLock и посмотреть на его реализацию, то видно, что он работает через CAS своего состояния.
На самом деле, принцип
happens-before
как раз и говорит о том, что запрещён instruction reordering и данные в КЭШах долны быть перезачитаны из RAM.Таким образом, volatile является самым быстрым вариантом межпоточного взаимодействия в Java.
И да, ставить модификатор volatile у каждой переменной класса и надеяться на то, что всё будет само по себе волшебно - это, конечно глупость. В случае, если область видимости данной переменной - это только один поток, то модификатор volatile только затормозит выполнение кода.
Модификатор volatile нужен именно для межпотокового взаимодействия и маркировать им нужно переменные понимая их жизненный цикл - из какого потока создаются, из какого - закрываются (выходят из области видимости всех потоков для сборки GC), из каких потоков читаются и из каких изменяются.
Здесь этот код только для того, чтобы показать как можно обойтись без блокировок в принципе на уже существующей в статье кодовой базе. В реальном продукте, такое использовать ни в коем случае нельзя, согласен с вами.
Когда начиналась наша шина данных (во времена становления Glassfish и JBoss), проект netty существовал только как часть JBoss (вроде не ошибаюсь). В то время, при исследовании производительности веб-сервисов на серверах Glassfish и JBoss (ещё спецификация EJB 2.x на xml-файлах без аннотаций и асинхронности), стало понятно, что их производительности не достаточно для telecom-нагрузок. Это стало той самой точкой, с которой началось развитие нашей шины на Java (более 15 лет назад).
Да, вы правильно поняли. Относительно асинхронности - все наши приложения полностью асинхронны с самого начала, как и шина. Есть, конечно, и синхронный API, но внутри он всё равно работает через асинхронный.
Запись с потока бизнес-логики уменьшает время отклика до определённого уровня нагрузки на данном железе. На принимающей стороне ответ также пытается уйти с потока бизнес-логики. У нас есть реальные сервисы, которые отвечают со временем отклика менее 1 мс. Многократное перекладывание с потока на поток ощутимо увеличивает время отклика при цепочке из нескольких вызовов.
Скажу даже больше: статья будет в помощь для довольно узкого круга проектов и большинству не понадобится даже "jdk.nio.maxCachedBufferSize", так как сейчас довольно небольшое число проектов имеют собственный сетевой стэк.
Не думаю, что для тех, кто пишет микро-сервисы при помощи аннотаций RestController, PostMapping и тому подобных, вообще интересно знание о том, как Spring работает с сетью.
Но, если кто-то написал свой сетевой клиент/сервер при помощи NIO, то вопрос производительности в этом случае, как правило, стоит остро. Искренне надеюсь, что для таких проектов статья будет полезна.
Решение с установкой "jdk.nio.maxCachedBufferSize", конечно, рабочее. Причём рабочее без изменения кода вообще, благодаря free при возвращении буфера:
Однако, такое решение прилично снизит пропускную способность, так как в этом случае мы имеем и копирование памяти из HeapByteBuffer в DirectByteBuffer и аллокацию direct-памяти при создании DirectByteBuffer.
На мой взгляд, для сервисов с небольшими нагрузками проще сразу работать с DirectByteBuffer и самому его аллоцировать.