The Atomic class in the concurrent package of java

The Atomic class in the concurrent package of java

sky ao IT ha ha
this is a real case, have provoked huge controversy, the cause of the story is very simple, is the need to implement a simple counter, then every value plus 1, so there is the following code:


          private int counter = 0;
          public int getCount ( ) {
                   return counter++;
          }

This counter is used to generate a sessionId, which is used to interact with the external charging system. This sessionId is of course required to be globally unique and not duplicated. But unfortunately, the above code was eventually found to produce the same id, so it will cause some inexplicable errors in some requests... More painfully, the above code is a tool class developed by other departments. We were I just took its jar package to call it, there is no source code, and I didn't think there would be such a low-level and terrible error in it.

Due to the repeated sessionId, some requests fail, although the probability of occurrence is extremely low, it is not always possible to reproduce the test every day. Because it is related to billing, even if the probability of error is low, it has to be resolved. The actual situation is that in the final stage of the project development, the final stability test before the release began. In the continuous test of 7*24 hours, this problem often reappeared only a few days after the start of the test, and I was responsible for trouble shooting. My colleagues were tossing miserably...After repeated searches, some people finally suspected that they came here, decompiled the jar package, and saw the above code in question.

This low-level error stems from a basic knowledge of java:
++ operations, whether i++ or ++i, are not atomic operations!

And a non-atomic operation, there will be thread safety issues under multi-threaded concurrency: here is a little explanation, the above "++" operator, in principle, it actually contains the following: calculate the new value after adding 1, and then Assign this new value to the original variable and return the original value. Similar to the code below


          private int counter = 0;
          public int getCount ( ) {
                   int result = counter;
                   int newValue = counter + 1; // 1. 计算新值
                   counter = newValue;         // 2. 将新值赋值给原变量
                   return result;
          }

When multiple threads are concurrent, if two threads call the getCount() method at the same time, they may get the same counter value. To ensure safety, one of the simplest methods is to synchronize on the getCount() method:


          private int counter = 0;
          public synchronized int getCount ( ) {
                   return counter++;
          }

In this way, the danger of concurrency caused by the non-atomic nature of the ++ operator can be avoided.
Let's extend this case a little bit. If the operation here is atomic, can concurrent access without synchronization and safety be possible? We will slightly modify this code:


          private int something = 0;
          public int getSomething ( ) {
                   return something;
          }
          public void setSomething (int something) {
                   this.something = something;
          }

Assuming that there are multiple threads concurrently accessing the getSomething() and setSomething() methods, when a thread sets a new value by calling the setSomething() method, can other methods that call getSomething() immediately read the new value? ? Here "this.something = something;" is an assignment to the int type. According to the Java language specification, the assignment to int is an atomic operation. There is no hidden danger of the non-atomic operation in the above case.

But there is still an important issue here, called "Memory Visibility". A series of knowledge about java memory model is involved here. It is limited in space and will not be described in detail. If you don’t know these knowledge points, you can look through the information yourself. The easiest way is to google these two keywords "java memory model", "java Memory visibility". Or, you can refer to this post "Java thread safety summary",

There are two ways to solve the "memory visibility" problem here, one is to continue to use the synchronized keyword, the code is as follows


          private int something = 0;
          public synchronized  int getSomething ( ) {
                   return something;
          }
          public synchronized  void setSomething (int something) {
                   this.something = something;
          }
 另一个是使用volatile 关键字,

          private volatile int something = 0;
          public int getSomething ( ) {
                   return something;
          }
          public void setSomething (int something) {
                   this.something = something;
          }

The solution using the volatile keyword is much better in terms of performance, because volatile is a lightweight synchronization that can only guarantee the visibility of multi-threaded memory, but cannot guarantee the orderly execution of multi-threaded. Therefore, the overhead is much smaller than synchronized.

Let's go back to the beginning of the case, because we use the modified method of adding synchronized directly before the getCount() method, thus not only avoiding the order of multi-threaded execution caused by non-atomic operations, but also "by the way" Solved memory visibility issues.

OK, now you can continue. As mentioned earlier, the problem can be solved by adding synchronized before the getCount() method, but there is actually a more convenient way to use the atomic classes provided in the concurrent package introduced after jdk 5.0. java.util.concurrent.atomic.Atomic***, such as AtomicInteger, AtomicLong, etc.


        private AtomicInteger  counter = new AtomicInteger(0);
        public int getCount ( ) {
             return counter.incrementAndGet();
        }

The Atomic class not only provides thread safety guarantee for data operations, but also provides a series of methods with clear semantics such as incrementAndGet(), getAndIncrement, addAndGet(), getAndAdd(), which are easy to use. More importantly, the Atomic class is not a simple synchronization package. Its internal implementation is not simply the use of synchronized, but a more efficient way of CAS (compare and swap) + volatile, thus avoiding the high overhead of synchronization and execution efficiency Greatly improved. Due to space limitations, the principle of "CAS" will not be discussed here.

Therefore, for performance reasons, it is strongly recommended to use the Atomic class as much as possible instead of writing code implementation based on the synchronized keyword.
Finally, to summarize, in this post we talked about several issues:
1. ++ operations are not atomic operations
2. Non-atomic operations have thread safety issues
3. Memory visibility under concurrency
4. Atomic classes can be achieved through CAS + volatile It is more efficient than synchronized and recommended

Guess you like

Origin blog.51cto.com/15061944/2593719