Lock lock

1. Why Lock

  1. Why is synchronized not enough, and lock is needed

       The two most common locks, Lock and synchronized, can achieve the purpose of thread safety, but their functions are very different.

       Lock is not used to replace synchronized but to provide advanced functions when using synchronized does not meet the situation or is inappropriate

  1. Why is synchronized not enough
  • Low efficiency : the release of locks is rare, the timeout cannot be set when trying to obtain the lock, and a thread that is trying to obtain the lock cannot be interrupted
  • Not flexible enough : when locking and releasing are single, only a single condition for each lock may not be enough
  • Unable to know whether the lock was successfully acquired

2. The meaning of Lock

  1. Compared with using synchronized methods and statements, the Lock implementation provides a wider range of lock operations. They allow for a more flexible structure, can have completely different properties, and can support multiple associated Condition objects.
  2. Lock is a tool used to control access to shared resources by multiple threads. Generally, locks provide exclusive access to shared resources. Only one thread can acquire the lock at a time, and all accesses to shared resources need to acquire the lock first . However, some locks may allow concurrent access to shared resources , such as ReadWriteLock's read lock.
  3. Use the synchronized method or statement to access the implicit monitor lock associated with each object, but it will force all locks to be acquired and released in a block structure. When acquiring multiple locks, they must release the locks in the reverse order.
  4. Although the scope mechanism used for synchronized methods and statements makes programming using monitor locks easier and helps avoid many common programming errors involving locks, in some cases you need to be more flexible Way to use locks. For example, some algorithms used to traverse concurrently accessed data structures need to use "handover" or "chain lock": you acquire the lock of node A, then acquire the lock of node B, then release A and acquire C, then release B and Get D etc. The implementation of the Lock interface enables this type of technology by allowing locks to be acquired and released in different scopes and allowing multiple locks to be acquired and released in any order.

3. The usage of the lock

       Increased flexibility brings additional responsibilities. The lack of a block structure lock requires manual release of the lock. In most cases, the following idioms should be used:

Lock lock = new ReentrantLock();
lock.lock();
try{
    
    
  
}finally {
    
    
  lock.unlock();
}

       When locking and unlocking occur in different scopes, care must be taken to ensure that all code executed while holding the lock is protected by try-finally or try-catch to ensure that the lock is released when necessary.
       The Lock implementation uses a non-blocking attempt to acquire a lock (tryLock()) and an attempt to acquire an interruptible lock (lockInterruptibly and an attempt to acquire a lock), providing more functions than using synchronized methods and statements. It may time out (tryLock(long, TimeUnit) ).

       The Lock class can also provide completely different behaviors and semantics from implicit monitor locking, such as guaranteed order, non-reusable or deadlock detection. If the implementation provides this special semantics, the implementation must document these semantics.

       Please note that Lock instances are just ordinary objects, and they themselves can be used as targets in synchronized statements. Obtaining the monitor lock of a Lock instance has no specified relationship with calling any lock method of that instance. It is recommended to avoid confusion and do not use Lock instances in this way unless you use them in your own implementation.

4. Memory synchronization

       All Lock implementations must enforce the same memory synchronization semantics provided by the built-in monitor lock, as described in the Java language specification:

  • A successful lock operation has the same memory synchronization effect as a successful lock operation.
  • A successful unlock operation has the same memory synchronization effect as a successful unlock operation.

       Unsuccessful lock and unlock operations and reentrant lock/unlock operations do not require any memory synchronization effects.

Implementation considerations

       The three forms of lock acquisition (interruptible, uninterruptible, and timing) may differ in their performance characteristics. In addition, in a given Lock class, the ability to interrupt an ongoing lock may not be provided. Therefore, there is no need to define exactly the same guarantee or semantic implementation for all three forms of lock acquisition, and there is no need to support interruption of ongoing lock acquisition. An implementation is needed to clearly document the semantics and guarantees provided by each locking method. Within the scope of supporting lock acquisition interruption, it must also obey the interruption semantics defined in this interface: all or only when the method is input .

5.The interface provided by Lock

image

5.1 Acquire a lock

void lock(); // 获取锁。
  1. The most common way to acquire a lock is to wait if the lock is acquired by other threads
  2. lock will not automatically release the lock when abnormal like synchronized
  3. Therefore, the lock must be released in finally to ensure that the lock must be released when an exception occurs

Note : the lock() method cannot be interrupted, which will bring great hidden dangers: once deadlock, lock() will fall into a permanent waiting state

5.2 Acquiring an interrupt lock

void lockInterruptibly() throws InterruptedException;

       Unless the current thread is interrupted, the lock is acquired.
       Acquire the lock (if any) and return immediately.

       If the lock is not available, for thread scheduling purposes, the current thread will be suspended and sleep before one of the following two situations occurs:

  • The lock is acquired by the current thread;
  • Some other threads interrupt the current thread and support the interruption of lock acquisition.

       If the current thread: its interruption status has been set when entering this method; or it is interrupted when the lock is acquired, and the interruption of lock acquisition is supported , then an InterruptedException is thrown and the interruption status of the current thread is cleared.

Precautions

       In some implementations, the ability to interrupt lock acquisition may not be possible, and may be an expensive operation if possible. The programmer should be aware that this may be the case. In this case, the implementation should be documented. The implementation may prefer to respond to interrupts than normal method returns. Lock implementations may be able to detect incorrect use of the lock, such as calls that may cause a deadlock, and in this case may raise (unchecked) exceptions.

Note that synchronized is uninterruptible when acquiring the lock

5.3 Attempt to acquire lock

boolean tryLock();

       Acquire the lock (if any) non-blocking and return true immediately. If the lock is not available, this method will return false immediately. Compared with the Lock method, it is obviously more powerful. We can determine the behavior of the subsequent program according to whether the lock can be obtained.
Note: This method will return immediately, even if the lock is not available, it will not be there. wait

The typical usage of this method is:

Lock lock = new ReentrantLock();
if(lock.tryLock()){
    
    
  try{
    
    
    // TODO
  }finally {
    
    
    lock.unlock();
  }
}else{
    
    
  // TODO
}

5.4 Acquire a lock within a certain period of time

boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

       If the thread acquires the lock within the given waiting time, and the current thread has not been interrupted, the lock is acquired.
       If the lock is available, this method immediately returns a true value. If the lock is not available, for thread scheduling purposes, the current thread will be suspended and sleep until one of the following three situations occurs:

  1. The lock is acquired by the current thread.
  2. Some other threads will interrupt the current thread and support the interruption of lock acquisition.
  3. If the lock is acquired after the specified waiting time, the return value is true.

       If the specified waiting time has elapsed, the return value is false. If the time is less than or equal to zero, the method will not wait at all .

Precautions

       In some implementations, the ability to interrupt lock acquisition may not be possible, and may be an expensive operation if possible. The programmer should be aware that this may be the case. In this case, the implementation should be documented. Implementations may prefer to respond to interrupts than normal methods returning or reporting timeouts. Lock implementations may be able to detect incorrect use of the lock, such as calls that may cause a deadlock, and in this case may raise (unchecked) exceptions.

5.5 Unlock

void unlock(); //释放锁。

Note
       Lock implementations usually restrict which threads can release the lock (usually only the holder of the lock can release the lock), and if this restriction is violated, an (unchecked) exception may be raised.

5.6 Get the waiting notification component

Condition newCondition(); //返回绑定到此Lock实例的新Condition实例。

       This component is bound to the current lock , and the current thread has only acquired the lock. The wait() method of the component can be called, and after the call, the current thread will release the lock.
Precautions

The exact operation of the Condition instance depends on the Lock implementation.

5.7 Summary

       Lock object locks also provide other synchronization features that synchronized does not have, such as the acquisition of interruptible locks (synchronized is not interruptible while waiting to acquire the lock), the acquisition of timeout interrupt locks, and the multi-condition variable Condition waiting for the wake-up mechanism . This also makes the Lock lock more flexible. Locking and releasing locks have the same memory semantics as synchronized, which means that after the next thread is locked, all operations that occurred before the previous thread is unlocked can be seen.

6. Classification of locks

A variety of different locks can be distinguished according to the following 6 situations, which are described in detail below

6.1 Do you want to lock synchronization resources

Is it locked Lock name Method to realize example
Lock post Pessimistic lock synchronized、lock synchronized、lock
Not locked Optimistic lock CAS algorithm Atomic class, concurrent container

Pessimistic locks are also called mutually exclusive synchronization locks. The disadvantages of mutually exclusive synchronization locks:

  1. Performance disadvantages caused by blocking and wakeup
  2. Permanent blocking: If the thread holding the lock is permanently blocked, such as infinite loops, deadlocks and other active issues
  3. Priority inversion

Pessimistic lock:

       When a thread acquires the lock, no other thread can acquire the lock, and the lock can only be acquired after the thread holding the lock releases the lock.

Optimistic lock:

       There will be no interference from other threads when you are operating yourself, so the object will not be locked. When updating, compare my data during the modification period and whether anyone has modified it. If there is no change, then modify it. If it is changed, it is someone else's. Then I will not change it and give up, or start over.

Cost comparison:

  1. The original cost of pessimistic locks is higher than that of optimistic locks, but the characteristic is once and for all. Even if the critical section holds locks for longer and longer, it will not affect the cost of mutex locks.
  2. The initial cost of pessimistic locking is smaller than that of optimistic locking, but if the spin time is long or if retrying constantly, more and more resources will be consumed

scenes to be used:

  1. Pessimistic lock: suitable for situations where there are many concurrent writes, suitable for situations where the critical section has a long lock time, pessimistic locks can be avoided, a lot of useless spins and other consumption
  2. Optimistic lock: suitable for scenarios where there are more concurrent reads, no lock can greatly improve the read performance

6.2 Can a lock be shared

Whether to share Lock name
can Shared lock (read lock)
Can't Exclusive lock (exclusive lock)

Shared lock:

       After acquiring the shared lock, you can view but cannot modify and delete the data. Other threads can also acquire the shared lock at this time, and can also view but cannot modify and delete the data.

Case: ReentrantReadWriteLock read lock (specific implementation will be explained in subsequent series of articles)

Exclusive lock:

       After acquiring an exclusive lock, other threads cannot acquire the current lock, such as a write lock.

Case: ReentrantReadWriteLock write lock (specific implementation will be explained in subsequent series of articles)

6.3 Whether to queue

Whether to queue Lock name
queue Fair lock
No queue Unfair lock

Unfair lock:

       First try to jump in the queue, and then fail to jump in the queue. Unfair means that the queue is not completely in accordance with the order of the request and can be jumped under certain circumstances.

The meaning of existence:

  • Improve efficiency
  • Avoid the gap caused by wake-up

Case:

  1. Take ReentrantLock as an example, the parameter is false when creating an object (the specific implementation will be explained in subsequent series of articles)
  2. For the tryLock() method, it does not comply with the fair rules set

       For example: when a thread executes tryLock once a thread releases the lock, the thread that is executing tryLock can immediately acquire the lock even if there are other threads in the waiting queue before it

Fair lock:

       Queuing, fairness refers to the allocation of locks in the order of thread request

Case: Taking ReentrantLock as an example, the parameter is true when creating the object (the specific implementation will be explained in subsequent series of articles)

note:

       Unfairness also does not promote the behavior of jumping the queue. The unfairness here refers to jumping the queue at the right time, not blindly jumping the queue

Advantages and disadvantages:

Unfair lock:

  • Advantages: faster and higher throughput
  • Disadvantages: possible thread starvation

Fair lock:

  • Advantages: Threads are equal, each thread has a chance to execute in order
  • Disadvantages: slower, smaller throughput

6.4 Can the same lock be acquired repeatedly

Is it possible to re-enter Lock name
can Reentrant lock
Can't Non-reentrant lock

Case: Take ReentrantLock as an example (the specific implementation will be explained in subsequent series of articles)

6.5 Can it be interrupted

Can it be interrupted Lock name Case
can Interruptible lock Lock is an interruptible lock (because tryLock and lockInterruptibly can respond to interrupts)
Can't Uninterruptible lock Synchronized is an uninterruptible lock

6.6 The process of waiting for the lock

Whether to spin Lock name
Yes Spin lock
no Blocking lock

scenes to be used:

  1. Spin locks are generally used for multi-core servers, and are more efficient than blocking locks when the concurrency is not very high.
  2. The spin lock is suitable for the case where the critical section is relatively short, otherwise if the critical section is large, once the thread gets the lock, it will be released a long time later. It is also inappropriate, because it will waste performance when spinning

7. Lock optimization

7.1 Lock optimization in virtual machine

  1. Spin lock
  2. Lock elimination
  3. Chain coarsening

These three lock optimization methods are explained in the previous Synchronized article

7.2 Lock optimization when writing code

  • Shrink the synchronization code block
  • Try not to lock the method
  • Reduce the number of lock requests
  • Avoid artificial hot spots
  • Try not to include locks in locks
  • Choose the right lock type or the right tool

Guess you like

Origin blog.csdn.net/weixin_38071259/article/details/112541717