“Disputes are not scary, and locking creates proper process coordination to dramatically increase productivity with existing resources.”

Previous article (Java System throughput not up? Java Concurrency Concurrency (1) We discuss the Java synchronization mechanisms (synchronized, volatile, and Atmoic) and their strengths and weaknesses.

In this article, we’ll discuss other high-level components that are built on volatile, atomic classes, And compare-and-swap.

As we know, the purpose of using multithreading is to improve the throughput of the system. In this way of thinking, we compromise on code complexity, troubleshooting difficulty, and monitoring to maximize performance. When you have a shared object, locks are inevitably used, and the shared object becomes a performance bottleneck. Therefore, choosing a suitable lock will have a significant effect on performance improvement.

But why isn’t synchronized enough?

  1. Synchronized is a strictly exclusive lock, and as a result, in most cases, it cannot be tailored to accommodate further performance improvements.

  2. In terms of fairness, you can’t take the JVM any further to ensure fairness.

  3. When deadlocks and thread starvation occur in the system after using Synchronized, the only solution is to terminate and restart the service system, because the thread is in an endless blocking state and there is no way to interrupt the blocked thread.

Study on locking mechanism

High version of the JDK’s own Java. Util. Concurrent. The locks, Lock interface, define the great flexibility, in order to meet the needs of customized optimization under different usage scenarios, but also avoid the disadvantage of synchronized. This interface defines the following methods:

  • Lock (): With the lock() method called, the thread will block until it acquires the lock, and there is no way to break a blocked lock.

  • LockInterruptibly (): After a method call, the thread will block until the lock is acquired, however, it is possible to interrupt a thread that is blocked if the lock is not acquired.

  • TryLock (): With this method, the calling thread does not block and returns immediately: true if the lock can be obtained, fasle otherwise.

  • TryLock (long, TimeUnit): When called, the thread blocks until a lock is acquired or a specified time timeout. This method returns true if the lock is acquired. Threads that are blocked in this case can also be interrupted.

  • UN lock() : Releases the acquired lock. Be sure to call this method in the finally block to release the lock.

  • NewC ondition(): This is one of the most useful locking features. In some usage scenarios, threads need to wait for some condition to be satisfied, and while waiting, it is necessary to release the held lock to allow other threads to continue executing. This mechanism is similar to object. wait and Object.notify/notifyAll for synchronized pairing.

   

ReentrantLock

ReentrantLock implements the above interface. The ReentrantLock class has two constructors that can choose whether to lock fairly or unfairly.

When using ReentrantLock, it is important to note that both fair and unfair locks use a wait queue to hold blocked threads, which means:

  • Thread priority is no longer in play. Thus, thread priority can no longer be relied upon in business logic.

  • The implementation mechanism of fairness is also simple. There is no complex scheduling mechanism, which means that a thread will starve if it does not release the lock or holds the lock for a long time.

Fair or not fair lock lock is to use AbstractQueuedSynchronizer, it implements the waiting queue, the waiting queue is derived based on CLH lock queue.

Logic for assigning locks:

  • Fair lock: a lock is assigned to a thread if the current thread already holds the desired lock, if no other thread has applied for the lock, and if the current thread is first in the queue.

  • Unfair lock: an attempt is made to acquire the lock, and if not, the current thread is placed in the thread’s wait queue.

Important guidelines for use:

  • Use of tryLock(). Unlike the other methods, this method is essentially an unfair implementation, whether it is a fair implementation or an unfair implementation. Thus, when using a fair lock, be aware that it may be inconsistent with expectations.

  • When using ReentrantLock locks, use the lock() method as sparingly as possible. Choose another method, and depending on the current usage scenario, we may need to add an exponential timeout or maximum retry times, or a combination of methods. Even if blocking is required in the current scenario, use the lockInterruptibly() or tryLock(long, Timeout) method while waiting for some business data to be in place. The code is as follows:

    

    

    boolean acquired = false;     long wait = 100;    int retries = 0;    int maxRetries = 10;    try {while (! acquired && retries < maxRetries) {            acquired = lock.tryLock(wait, TimeUnit.MILLISECONDS);            wait *= 2;            ++retries;        }if (! acquired) {            // log error or throw exception        }    } catch (InterruptedException e) {        // log error or throw exception    } finally {        lock.unlock();    }Copy the code
  • One bad case is that interrupts from blocking threads are ignored. Interrupts of blocked threads should be handled appropriately to avoid problems related to business systems or thread pools. Even if it is determined that it can be ignored, the exception is printed in the log rather than ignored directly.

  • The ReentrantLock class provides other methods that are suitable only for Bug detection or health display, not thread coordination. My advice is to stick to in the code using Java. Util. Concurrent. The locks, Lock interfaces declared in the method, rather than in your own code using the above mentioned screening methods, such as bugs, let your own code contaminated, after all the Bug screen problem can use other tools is more elegant.

ReentrantReadWriteLock

ReentrantReadWriteLock has two reentrant locks behind it: a read lock and a write lock. Read locks can be shared (that is, read locks can be held by multiple threads if the write lock is not held). Write locks are a bit different. Write locks are strictly exclusive and can only be acquired by a maximum of one thread if the read lock is not allocated. This lock is useful in most situations and can greatly improve synchronization performance.

This lock implementation also has both fair and unfair strategies.

Some other thread coordination tools:

  • CountDownLatch: Useful when multiple threads need to wait for a set of actions to complete before continuing. When used, a count is passed to the constructor and the countDown() method is called upon completion of each operation. When the incoming count is 0, the thread waiting outside can continue.

  • CyclicBarrier: This tool is useful in business scenarios where multiple threads have to wait for a common point to proceed. Of course, the same effect can be achieved using the previous CountDownLatch method in a roundabout way, but using CyclicBarrier has some other benefits:

    • Counts in cyclicBarriers can be reset.

    • CyclicBarrier can have a Runnable implementation that triggers execution of the barrier when it arrives.

    • If one of the managed threads leaves, the Barrier assumes that it is corrupt. This can happen for a variety of reasons, such as thread interrupts, failures, or wait timeouts. If the Barrier is broken, other waiting threads are notified to leave the Barrier by BarrierBrokenException. The state of the barrier can be detected by the isBroker() method. The barrier state is held until the reset() method is called.

  • StampedLock: This is another read-write lock. But the Lock does not implement the aforementioned Java. Util. Concurrent. The locks. Lock interface. StampedLock is also more complex to use than ReentrantReadWriteLock. It does, however, have a meaningful feature called optimistic reading, which is tricky and fragile to use. ReentrantReadWriteLock is strongly recommended to replace StampedLock.

———

Phase to recommend

  1. Java system throughput not up? Java Concurrency is probably not used properly

  2. Sinology thinking and software construction

  3. Some Essential Thinking Patterns for Software Architect (PART 1)

  4. Some Essential Thinking Patterns for Software Architect (PART 2)

~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~

Long press the QR code to follow the official account

We will work together to promote the informatization of e-commerce business