Статьи

5 кратких замечаний о потоках для Java EE

Идея параллельного выполнения кода и быстрого выполнения работы всегда соблазнительна. И, конечно, здорово писать многопоточный код !! Тем не менее, очень немногие из нас фактически вызывают потоки или инициируют параллельную обработку. Итак, мы используем темы изо дня в день или нет? Должны ли мы рассмотреть вопросы безопасности потоков?

Ответьте на них, прежде чем двигаться дальше:

Вы написали код для запуска / вызова / порождения потока?

  • Если вы не вызывали поток, возможно ли, что ваш код работает в многопоточной среде?
  • Предписала ли ваша организация нормы безопасности нитей? Они полезны для вас?
  • В вашем обзоре кода упоминается безопасность потоков?
  • Знаете ли вы, почему не потокобезопасен, может использоваться в многопоточных приложениях?

Вот несколько советов о потоках в Java EE

  1. Вы не можете избежать думать о нитях
  2. Есть два отличных аспекта обработки потоков — Пересечение и Безопасные зоны
  3. Определить переходы и безопасные зоны — это легко 
  4. Убедитесь, что безопасные зоны надежно безопасны 
  5. Обращайтесь с переходами осторожно

Вы не можете избежать думать о нитях

DoGet и doPost вашего сервлета обычно вызываются в нескольких потоках. Ваши сессионные компоненты также обычно вызываются в нескольких потоках (также могут быть MDB, задания Cron и т. Д.). Таким образом, поскольку каждый другой фрагмент кода вызывается из одного из этих источников, вся ваша кодовая база доступна для нескольких потоков.

Но не нужно беспокоиться … темы не так сложно контролировать. Кроме того, обратите внимание, что попытка избежать многопоточности там, где она естественным образом соответствует сценарию использования, может привести к серьезным проблемам с производительностью. С другой стороны, как только вы прочитаете и поймете эту статью, вы можете «отключить контроль тяги» и выполнять трюки!

Есть два отличных аспекта обработки потоков

Представьте себе два высокоскоростных поезда, идущие параллельно в одном направлении. Совершенно очевидно, что если их трассы не пересекаются на большие расстояния, не о чем беспокоиться. Должно быть легко понять, что поезда иногда должны пересекать железнодорожные пути друг друга, поэтому мы позволяем им пересекаться в «контролируемых точках» и хорошо организовывать пересечение, чтобы избежать несчастных случаев. Аналогично для нитей, вы должны «изолировать пересечения» и сделать «безопасные зоны» видимыми и, очевидно, безопасными. 

Переходы и безопасные зоны легко определить

Во-первых, понять пересечения. Существуют веские причины, по которым нам нужны общие объекты: «Повторное использование дорогостоящих объектов», «Общие ресурсы» и «Статическое состояние» — все это важно. Полезно понять, как объекты распределяются между потоками. Вам нужно только отслеживать несколько строк кода, которые используются для  предоставления  или  доступа к  этим общим ресурсам. Остальные части вашей кодовой базы по существу потокобезопасны и составляют безопасную зону.

Справочная информация — как выполняются приложения Java EE

  1. Приложения запускаются с одного основного потока —  основного  потока, который работает, поддерживая работу приложения. Обычно это часть контейнера в Java EE
  2. При получении ввода от пользователя или при получении запроса от клиента главный поток может запустить новый поток (ы), чтобы выполнить работу. Для вашего управляемого компонента вызывается некоторый метод ввода, например, doGet или doPost или бизнес-метод компонента
  3. Начиная с этого момента, вы не создаете никаких дополнительных потоков, поэтому вы программируете с одной мыслью.
  4. По мере выполнения вашего кода вы можете создавать объекты с  новыми  и работать с ними. 
  5. Вы можете поделиться несколькими из этих объектов с контейнером или хранить статическую ссылку на объекты — они могут стать доступными для других потоков
  6. Ваш код также может обращаться к предоставленным контейнером объектам, синглетам (и другим объектам, хранящимся в статических ссылках) и т. Д., Которые могут быть созданы другими потоками и видимы для вас.  

Убедитесь, что безопасные зоны надежно безопасны

Every object that you create with new (or you have written a factory method which does new) are thread-safe by default. Only if you share these objects in a static context or to the container they may become accessible by other threads. Here are some examples of sharing:

  • In static context 
//Static field. Could be acccessed by multiple threads
FormatterUtil.sharedFormatter = new MyFormatter(); 
  • To container
//Container can share this object...
session.setAttribute("sharedFormatter", new MyFormatter());
  • Shared because parent is already shared
//If globalObject is already shared, this new formatter object is also exposed
globalObject.setFormatter(new MyFormatter());

Unless you are doing one of the above operations, the rest of your code is totally safe! Specifically, if an object’s reference is held only by private instance variables and local variables, that object is inaccessible to any other thread and is totally thread-safe (private variables which don’t have a public setter method).

How to visibly show code is safe
  1. Write minimum code in static methods- implement business logic as instance methods not static methods. Even if you write static methods, avoid static instance variables (just do some work, don’t store anything) 
  2. Don’t expose: Keep as many objects ‘not shared’ as possible — For example, don’t write getters and setters for internal variables.
  3. Use lots of new and don’t unnecessarily reuse — For example don’t reuse a SimpleDateFormat. Just create one each time you need to format or parse a date (or keep it as a private instance variable and mark it final. But don’t make it static. Once you make it static it can be accessed by multiple threads).
  4. Similarly, create new ArrayLists and StringBuilders freely… don’t reuse. (Code reuse is good, object reuse is bad)
  5. Create new instances every time for custom objects also. For example, create new BO and DAO objects. Use the objects the throw them (let them get garbage collected). Don’t try to reuse. 
  6. For extra safety, make task-oriented objects like BO/Bean and DAO stateless as far as possible (by avoiding instance variables).
  7. Make shared objects read-only (immutable) as far as possible: For example, if you have a list of countries supported by your application, that list is not going to change. So, mark the reference as final and use immutable lists/maps (See: http://stackoverflow.com/questions/7713274/java-immutable-collections).

To reiterate, limit scope and encapsulate data and share only ‘very-heavy’ resources. Realize that 
new is safe and object reuse is risky (except when it is unavoidable).  For shared objects, use immutable data types and mark references as final wherever possible. 
In short: don’t allow any freedom that is not required. Remember, discipline is always good. It will save you so much trouble in the long run (See: 
http://stackoverflow.com/questions/490420/favorite-clever-defensive-programming-best-practices). Also, there are huge performance benefits to clearly demarcating safe and not-safe zones.

Benefits of Visibly safe code
Once you know which parts of your code are safe, you can get awesome performance gains by using not-thread-safe objects! Here are two of the best examples:
  1. You can freely use ArrayLists and HashMaps which are not-thread-safe. When you know that objects created in local context are totally thread safe you can freely use these objects.
  2. You can freely use StringBuilders which are not-thread-safe. Again, you can use it freely in local context (and sometimes keep it as private instance variable and reuse as well)

Handling the crossings

Heavy resources and constrained resources may have to be shared. So, they need to be managed. This is achieved by synchronization.

Very early on, you should understand that there are two levels of analyzing synchronization. One is at the method level or ‘functional operation’ level. The second is at the task level. Taking care of both these aspects is critical and neither one is more or less important than the other.

The first one is commonly handled by marking the static accessor method of the shared resource as ‘synchronized’. This is not fool proof but it is a good starting point. The second one is usually handled by starting a synchronized block with a lock on the shared resource. This ensures that other threads do no interfere between consecutive calls to various methods on the shared resource. If you understand that synchronization has to be analysed at multiple levels you will be off to a good start.

Why are thread-safe objects useless by themselves

You have to understand that thread-safe objects only provide level one of safety as mentioned above. They only ensure that each operation/method is atomic. They ensure that their state is consistent during a single operation like ‘get’ or ‘put’. That is all they can do for you. This is why thread-safe objects are not useful on standalone basis.

The second level of safety is left to you to manage as it is part of your business requirement. Only you know if or when your get is going to be followed by a put. For example, when checking an account balance first level safety is enough (a safe get). But, when you want to ensure balance and then deduct, then you need the second level of safety (acquire lock, get, update/put and finally release lock). So, in this example, for the second operation you will have to use synchronized block.

Conclusion

Objects are lying on the heap. References to those objects are held in static fields or on stacks of threads. All objects are created by some thread, and the references are essentially local unless the reference is shared through static fields. Sharing is also possible through libraries/utilities/execution environment. Every line of code that does not access shared resources can be considered safe.

Know how your application is exposed to threads: familiarize yourself with sources of threads in your application (usually container/libraries unless you are initializing threads by yourself). Once you have a picture of the concentration of thread-risk in various classes in your application, managing threading can be easy. 

Keep the safe zones very clean: Limit scope as much as possible. Create, use and throw objects freely. Limit sharing of objects. To handle the shared resources, realize that thread safety is at two levels. Using thread-safe objects does only half the work. On the other hand, in safe contexts, freely use thread-unsafe objects. You will get huge performance gains without any risk if you use thread-unsafe objects in the correct context.

Further reading

  1. Defensive programming style
  2. Immutable collections
  3. Guava — Google’s open source library — contains some of the best immutable collection implementations: https://code.google.com/p/guava-libraries/wiki/ImmutableCollectionsExplained 
  4. ArrayList and StringBuilder discussions on Stackoverflow