1. LongAdder
It is used in a similar way to AtomicLong, but has better performance than AtomicLong.
LongAdder and AtomicLong both use atomic operations to improve performance. However, LongAdder performs hot spot separation based on AtomicLong. Hot spot separation is similar to reducing the lock particle size in locked operation, separating a lock into several locks to improve performance. In lock-free, similar methods can be used to increase the success rate of CAS, thereby improving performance.
LongAdder schematic:
The implementation method of AtomicLong is that there is a value variable inside. When multiple threads are self-increasing and self-decreasing, they are all operated from the machine instruction level through CAS instructions to ensure the atomicity of concurrency. The only reason that restricts AtomicLong's efficiency is high concurrency. High concurrency means that the CAS has a higher chance of failure, more retry times, and the more threads retry, the higher the chance of failure of CAS, which becomes a vicious cycle, and the efficiency of AtomicLong is reduced.
LongAdder will split a value into several cells, and add up all cells to the value. Therefore, when adding and subtracting LongAdder, you only need to operate on different cells. Different threads perform CAS operations 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 does not have an API for multiplication and division).
However, when the concurrency number is not very high, splitting into several cells also requires maintaining the cell and summing, which is not as efficient as AtomicLong's implementation. LongAdder used a clever way to solve this problem.
In the initial situation, LongAdder and AtomicLong are the same. Only when CAS fails, the value will be split into cells. Each time the failure is made, the number of cells will be increased. This is also 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 strategy of exchanging space for time.
2. CompleteFuture
Implement the CompletionStage interface (more than 40 methods), most of which are used in functional programming. And support streaming calls
CompleteFuture is an enhanced version of Future in Java 8
Simple implementation:
import java.util.concurrent.CompletableFuture;public class AskThread implements Runnable { CompletableFuture<Integer> re = null; public AskThread(CompletableFuture<Integer> re) { this.re = re; } @Override public void run() { int myRe = 0; try { myRe = re.get() * re.get(); } catch (Exception e) { } System.out.println(myRe); } public static void main(String[] args) throws InterruptedException { final CompletableFuture<Integer> future = new CompletableFuture<Integer>(); new Thread(new AskThread(future)).start(); // Simulate a long-term calculation process Thread.sleep(1000); // Inform the completion result future.complete(60); }} The most criticized thing about Future is that you have to wait and check whether the task has been completed by yourself. In Future, the time for the task to be completed is uncontrollable. The biggest improvement of CompleteFuture is that the time for task completion is also open.
future.complete(60);
Used to set the completion time.
Asynchronous execution of CompleteFuture:
public static Integer calc(Integer para) { try { // Simulate a long execution Thread.sleep(1000); } catch (InterruptedException e) { } return para * para; } public static void main(String[] args) throws InterruptedException, ExecutionException { final CompletableFuture<Integer> future = CompletableFuture .supplyAsync(() -> calc(50)); System.out.println(future.get()); } Streaming call of CompletableFuture: public static Integer calc(Integer para) { try { // Simulate a long execution Thread.sleep(1000); } catch (InterruptedException e) { } return para * para; } public static void main(String[] args) throws InterruptedException, ExecutionException { CompletableFuture<Void> fu = CompletableFuture .supplyAsync(() -> calc(50)) .thenApply((i) -> Integer.toString(i)) .thenApply((str) -> "/"" + str + "/"") .thenAccept(System.out::println); fu.get(); }Combine multiple CompletableFutures:
public static Integer calc(Integer para) { return para / 2; } public static void main(String[] args) throws InterruptedException, ExecutionException { CompletableFuture<Void> fu = CompletableFuture .supplyAsync(() -> calc(50)) .thenCompose( (i) -> CompletableFuture.supplyAsync(() -> calc(i))) .thenApply((str) -> "/"" + str + "/"") .thenAccept(System.out::println); fu.get(); } These examples focus more on some new features of Java 8. Here are some examples to illustrate the features, so I won’t go into it in depth.
CompleteFuture has little to do with performance, but is more important to support functional programming and enhancement of functions. Of course, the setting of completion time is a highlight.
3. StampedLock
In the previous article, lock separation was just mentioned, 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 read should not block writes, and StampedLock believes that when reading and writing are mutually exclusive, read should be reread, rather than not allowing the writing thread to write. This design solves the problem of writing thread hunger when reading more and writing less.
So StampedLock is an improvement that tends to write threads.
StampedLock example:
import java.util.concurrent.locks.StampedLock;public class Point { private double x, y; private final StampedLock sl = new StampedLock(); void move(double deltaX, double deltaY) { // an exclusively locked method long stamp = sl.writeLock(); try { x += deltaX; y += deltaY; } finally { sl.unlockWrite(stamp); } } double distanceFromOrigin() { // A read-only method long stamp = sl.tryOptimisticRead(); double currentX = x, currentY = y; if (!sl.validate(stamp)) { stamp = sl.readLock(); try { currentX = x; currentY = y; } finally { sl.unlockRead(stamp); } } return Math.sqrt(currentX * currentX + currentY * currentY); }}The above code simulates the writing thread and the reading thread. StampedLock checks whether it is mutually exclusive according to the stamp. When writing a stamp once, it increases a certain value.
tryOptimisticRead()
It is the situation where reading and writing are not mutually exclusive as mentioned.
Every time you read a thread, you will first make a judgment
if (!sl.validate(stamp))
In validate, it will first check whether there is a writing thread writing, and then determine whether the input value is the same as the current stamp, that is, determine whether the read thread will read the latest data.
If there is a writing thread writing, or the stamp value is different, the return fails.
If the judgment fails, of course you can try to read it repeatedly. In the example code, it is not allowed to try to read it repeatedly, but instead uses the optimism lock to be degenerated into ordinary read locks to read it. This situation is a pessimistic reading method.
stamp = sl.readLock();
StampedLock implementation idea:
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 no successful threads are recorded in this queue. Each node (one node represents a thread) saves a locked bit to determine whether the current thread has released the lock. When a thread tries to acquire a lock, it obtains the tail node of the current waiting queue as its predecessor node. And use codes like the following to determine whether the preambled node has successfully released the lock
while (pred.locked) {
}
This loop is to wait for the previous node to release the lock, so that the current thread will not be suspended by the operating system, thereby improving performance.
Of course, there will be no endless spins, and the thread will be suspended after several spins.