Java high concurrency 10: detailed explanation of jdk8’s new support for concurrency
1. LongAdder
It is used in a similar way to atomiclong, but its performance is better than atomiclong.
Both longadder and atomiclong use atomic operations to improve performance. However, longadder performs hot spot separation on the basis of atomiclong. Hot spot separation is similar to reducing lock granularity in lock operation. It separates a lock into several locks to improve performance. In lock free, a similar method can be used to increase the success rate of CAS and improve performance.
Schematic diagram of longadder:
Atomiclong is implemented by an internal value variable. When multithreading increases and decreases concurrently, the concurrency atomicity is guaranteed from the machine instruction level through CAS instructions. The only reason that restricts atomiclong's efficiency is high concurrency. High concurrency means that CAS has a higher probability of failure and more retries. The more threads retry, the higher the probability of CAS failure, which becomes a vicious circle and reduces atomiclong's efficiency.
Longadder will split a value into several cells, and add up all cells to get value. Therefore, to add and subtract longadder, you only need to operate on different cells. Different threads operate CAS on different cells. Of course, the success rate of CAS is high (imagine 3 + 2 + 1 = 6, one thread 3 + 1, the other thread 2 + 1, and finally 8. Longadder has no API for multiplication and division).
However, when the concurrent number is not very high, splitting into several cells also needs to maintain the cell sum, which is not as efficient as the implementation of atomiclong. Longadder solves this problem in a clever way.
Initially, longadder is the same as atomiclong. Only when CAS fails, will value be split into cells. Each failure will increase the number of cells, which is equally efficient in low concurrency. In high concurrency, this "adaptive" processing method will not fail after reaching a certain number of cells, and the efficiency will be greatly improved.
Longadder is a space for time strategy.
2. CompletableFuture
It implements the completionstage interface (more than 40 methods), most of which are used in functional programming, and supports streaming calls
Completable future is an enhanced version of future in Java 8
Simple implementation:
The most critical thing about future is to wait and check whether the task has been completed. In future, the time of task completion is uncontrollable. The biggest improvement of completable future is that the time for task completion is also open.
future. complete(60);
Used to set the completion time.
Asynchronous execution of completable future:
Combine multiple completabilefuture:
These examples focus more on some new features of Java 8. Here are some simple examples to illustrate the features. Completable future has little to do with performance, but more to support functional programming and enhance functions. Of course, the setting of completion time is a highlight.
3. StampedLock
Lock separation was just mentioned in the previous article, and the important implementation of lock separation is readwritelock. Stampedlock is an improvement of readwritelock. The difference between stampedlock and readwritelock is that stampedlock believes that reading should not block writing. Stampedlock believes that when reading and writing are mutually exclusive, reading should be reread instead of not allowing the writing thread to write. This design solves the problem of writing thread starvation when using readwritelock when reading more and writing less.
Therefore, stampedlock is an improvement in favor of writing threads.
Stampedlock example:
The above code simulates a write thread and a read thread. Stampelock checks whether they are mutually exclusive according to the stamp. Write a stamp and increase a value
tryOptimisticRead()
This is the case just mentioned that reading and writing are not mutually exclusive.
Each time the reading thread wants to read, it will judge first
if (!sl.validate(stamp))
In validate, you will first check whether a write thread is writing, and then judge whether the entered value is the same as the current stamp, that is, judge whether the read thread will read the latest data.
If a write thread is writing, or the stamp value is different, a failure is returned.
If the judgment fails, of course, you can repeat the attempt to read. In the example code, you do not let it repeat the attempt to read, but use the optimistic lock to degenerate into an ordinary read lock to read. This is a pessimistic reading method.
stamp = sl.readLock();
Implementation idea of stampedlock:
CLH spin lock: when the lock application fails, the read thread will not be suspended immediately. A waiting thread queue will be maintained in the lock. All threads that apply for locks but fail will be recorded in this queue. Each node (a node represents a thread) stores a locked flag to determine whether the current thread has released the lock. When a thread attempts to obtain the lock, it obtains the tail node of the current waiting queue as its preamble node. It uses a code similar to the following to determine whether the preamble node has successfully released the lock
while (pred.locked) { }
This loop keeps waiting for the previous node to release the lock. This spin makes the current thread not suspended by the operating system, thus improving the performance. Of course, there will be no endless spins, and the thread will hang after several spins.