Read/write locks in Java concurrent packages and their Implementation Analysis

Source: Internet
Author: User

Read/write locks in Java concurrent packages and their Implementation Analysis

1. Preface

Usually used locks such as ReentrantLock in Java concurrent packages are usually exclusive locks. These locks can be accessed by only one thread at a time, the read and write locks can be accessed by multiple read threads at the same time. However, when the write thread accesses the lock, all read threads and Other write threads are blocked. The read/write lock maintains a pair of locks, one read lock and one write lock. By separating the read lock and write lock, the concurrency is greatly improved compared with the general exclusive lock.

In addition to improving the visibility and concurrency of write operations, read/write locks can simplify programming in read/write interaction scenarios. Assume that a shared data structure is defined in the program as a cache, and it provides read services such as query and search most of the time, while write operations take very little time, however, the update after the write operation is complete must be visible to the subsequent read service.

Before Java 5 is supported by the read/write lock), if you need to complete the above work, you must use the Java wait notification mechanism, that is, when the write operation starts, all read operations later than the write operation will enter the waiting state. Only after the write operation is completed and the notification is sent, all the pending read operations can continue to be synchronized between write operations based on the synchronized keyword ), in this way, the read operation can read the correct data without dirty reading. Use the read/write lock instead to implement the above functions. You only need to obtain the read lock during the read operation, but you can obtain the write lock during the write operation. When the write lock is obtained, it is not the current write operation thread) all read/write operations will be blocked. After the write lock is released, all operations will continue to be executed. The programming method is simpler and clearer than the implementation method using the wait notification mechanism.

Generally, read/write locks have better performance than the exclusive locks, because in most scenarios, read is more than write. When there are more reads than writes, the read/write locks can provide better concurrency and throughput than the exclusive locks. The implementation of the read/write locks provided by Java and the package is ReentrantReadWriteLock, and its features are shown in table 1.

Table 1. ReentrantReadWriteLock features

Features

Description

Fairness Selection

Support unfair (default) and fair lock acquisition methods, throughput or unfair is better than fair

Reenter

This lock supports re-entry. Taking the read/write thread as an example: after obtaining the read lock, the read thread can obtain the read lock again. The write thread can obtain the write lock again after obtaining the write lock, and also can obtain the read lock.

Lock downgrade

Follow the order of getting the write lock, getting the read lock, and then releasing the write lock. The write lock can be downgraded to a read lock.

2. read/write lock interface and example

ReadWriteLock only defines two methods for obtaining the Read and Write locks, namely the readLock () and writeLock () methods, and its implementation-ReentrantReadWriteLock, in addition to interface methods, some methods are provided to facilitate external monitoring of the internal working status, as shown in table 2.

Table 2. ReentrantReadWriteLock

Method Name

Description

Int getReadLockCount ()

Returns the number of times the read lock is obtained. The number of read locks is not equal to the number of threads used to obtain the read lock. For example, if only one thread obtains the read lock and re-enters the lock continuously) for n times, the number of threads occupying the read lock is 1, but this method returns n

Int getReadHoldCount ()

Returns the number of times the current thread has read locks. This method is added to ReentrantReadWriteLock in Java 6, and ThreadLocal is used to save the number of times the current thread obtains. This makes Java 6 implementation more complex.

Boolean isWriteLocked ()

Determine whether a write lock is obtained

Int getWriteHoldCount ()

Returns the number of times the current write lock has been obtained.

The following uses a cache example to describe how to use the read/write lock. The sample code is shown in code list 1.

Code List 1. Cache. java

 
 
  1. Public class Cache {
  2. Static Map <String, Object> map = new HashMap <String, Object> ();
  3. Static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock ();
  4. Static Lock r = rwl. readLock ();
  5. Static Lock w = rwl. writeLock ();
  6. // Obtain the value corresponding to a key
  7. Public static final Object get (String key ){
  8. R. lock ();
  9. Try {
  10. Return map. get (key );
  11. } Finally {
  12. R. unlock ();
  13. }
  14. }
  15. // Set the value corresponding to the key and return the old value
  16. Public static final Object put (String key, Object value ){
  17. W. lock ();
  18. Try {
  19. Return map. put (key, value );
  20. } Finally {
  21. W. unlock ();
  22. }
  23. }
  24. // Clear all content
  25. Public static final void clear (){
  26. W. lock ();
  27. Try {
  28. Map. clear ();
  29. } Finally {
  30. W. unlock ();
  31. }
  32. }
  33. }

In the preceding example, the Cache combines a non-thread-safe HashMap as the Cache implementation, and uses the read/write lock and write lock of the read/write lock to ensure that the Cache is thread-safe. In the get (String key) method of the read operation, you need to obtain the read lock, so that concurrent access to this method will not be blocked. Write operations put (String key, Object value) and clear () methods. When updating HashMap, you must obtain the write lock in advance. After the write lock is obtained, other threads are blocked in obtaining the Read and Write locks. Other read/write operations can continue only after the write locks are released. The Cache uses read/write locks to improve the concurrency of read operations. It also ensures the visibility of all read/write operations for each write operation and simplifies programming.

3. Implementation Analysis of read/write locks

Next, we will analyze the implementation of ReentrantReadWriteLock, including: the design of the read/write status, the acquisition and release of the write lock, the acquisition and release of the read lock, and the lock downgrade do not indicate that the read/write lock can be considered as ReentrantReadWriteLock ).

3.1 read/write status Design

The read/write locks also rely on custom synchronizers for synchronization, And the read/write status is the synchronization status of their synchronizers. Recall the implementation of the custom synchronizator in ReentrantLock. The synchronization state indicates the number of times the lock is repeatedly obtained by a thread, and the custom synchronizator in the read/write locks must have an integer variable in the synchronization state) maintain the status of multiple read threads and one write thread, so that the design of this status becomes the key to the implementation of read/write locks.

If you maintain multiple States on an integer variable, you must use the "use by bit" variable. The read/write lock splits the variable into two parts, and the high 16-bit value indicates read, the lower 16 bits indicate write, as shown in method 1.

Figure 1. Division of read/write lock status

As shown in figure 1, the current synchronization status indicates that a thread has obtained the write lock, re-entered twice, and obtained two read locks consecutively. How does a read/write lock quickly determine the status of read/write operations? The answer is bitwise. Assume that the current synchronization status value is S, and the write status equals S & 0x0000FFFF to erase all the 16-bit high), and the read status equals S >>> 16 unsigned complement 0 shifted to 16-bit ). When the write status increases by 1, it is equal to S + 1. When the read status increases by 1, it is equal to S + (1 <16), that is, S + 0 × 00010000.

According to the division of States, we can draw an inference: When S is not equal to 0, when S & 0x0000FFFF is equal to 0, the read status (S >>> 16) is greater than 0, that is, the read lock has been obtained.

3.2 acquisition and release of write locks

A write lock is an exclusive lock that supports re-entry. If the current thread has obtained the write lock, the write status is increased. If the current thread is not 0 when obtaining the write lock, or the thread is not a thread that has obtained the write lock, the current thread enters the waiting status, the code for obtaining the write lock is shown in code list 2.

Code List 2. ReentrantReadWriteLock's tryAcquire Method

 
 
  1. Protected final boolean tryAcquire (int acquires ){
  2. Thread current = Thread. currentThread ();
  3. Int c = getState ();
  4. Int w = exclusiveCount (c );
  5. If (c! = 0 ){
  6. // The read lock exists or the current acquiring thread is not the thread that has obtained the write lock
  7. If (w = 0 | current! = GetExclusiveOwnerThread ())
  8. Return false;
  9. If (w + exclusiveCount (acquires)> MAX_COUNT)
  10. Throw new Error ("Maximum lock count exceeded ");
  11. SetState (c + acquires );
  12. Return true;
  13. }
  14. If (writerShouldBlock () |! CompareAndSetState (c, c + acquires )){
  15. Return false;
  16. }
  17. SetExclusiveOwnerThread (current );
  18. Return true;
  19. }

In addition to the re-entry condition, the current thread is the thread for obtaining the write lock), this method adds a judgment on whether the read lock exists. If a read lock exists, the write lock cannot be obtained because the read/write lock must make sure that the write Lock operation is visible to the read lock. If a read lock is allowed to be acquired, other reading threads that are running cannot perceive the operation of the current write thread. Therefore, only when the read locks of other read threads are released can the write locks be acquired by the current thread. Once the write locks are acquired, subsequent access to other read/write threads is blocked.

The release process of the write lock is similar to that of the ReentrantLock. Each release reduces the write status. When the write status is 0, the write lock has been released, so that the waiting read/write thread can continue to access the read/write lock, and the modification of the previous write thread is visible to the subsequent read/write threads.

3.3 read lock acquisition and release

A read lock is a shared lock that supports re-entry. It can be obtained by multiple threads at the same time. When no other write thread accesses the lock or the write status is 0), the read lock will always be obtained successfully, what we do is thread-safe) to increase the read status. If the current thread has obtained the read lock, the read status is increased. If the write lock is obtained by another thread when the current thread acquires the read lock, it enters the waiting state. The implementation of obtaining the read lock has become much more complex from Java 5 to Java 6, mainly because some new functions, such as the getReadHoldCount () method, return the number of times the current thread acquires the read lock. The read status is the total number of read locks obtained by all threads. The number of read locks obtained by each thread can only be saved in ThreadLocal and maintained by the thread itself, this makes the implementation of read locks more complicated. Therefore, the code for obtaining the read lock is removed and necessary parts are retained, as shown in code listing 3.

Code List 3. tryAcquireShared method of ReentrantReadWriteLock

 
 
  1. protected final int tryAcquireShared(int unused) { 
  2.   for (;;) { 
  3.     int c = getState(); 
  4.     int nextc = c + (1 << 16); 
  5.     if (nextc < c) 
  6.       throw new Error("Maximum lock count exceeded"); 
  7.     if (exclusiveCount(c) != 0 && owner != Thread.currentThread()) 
  8.       return -1; 
  9.     if (compareAndSetState(c, nextc)) 
  10.       return 1; 
  11.   } 

In the tryAcquireShared (int unused) method, if other threads have obtained the write lock, the current thread fails to get the read lock and enters the waiting state. If the current thread obtains the write lock or the write lock is not obtained, the current thread (thread security, guaranteed by CAS) increases the read status and successfully acquires the read lock.

Every release of the read lock is thread-safe. Multiple read threads may release the read lock at the same time.) The read status is reduced by 1 <16 ).

3.4 lock downgrade

Lock downgrade means that the write lock is downgraded to a read lock. If the current thread has a write lock, release it, and finally obtain the read lock, this multipart completion process cannot be called lock degradation. Lock downgrade refers to the process of holding the current) Write lock, obtaining the read lock, and then releasing the previous) Write lock.

Next, let's look at an example of lock degradation: because data does not change frequently, multiple threads can concurrently process data. When data changes, if the current thread perceives data changes, data preparation is performed, and other processing threads are blocked until the current thread completes data preparation. The sample code is shown in Listing 4.

Code list 4. processData Method

 
 
  1. Public void processData (){
  2. ReadLock. lock ();
  3. If (! Update ){
  4. // The read lock must be released first
  5. ReadLock. unlock ();
  6. // Lock downgrade starts when the write lock is obtained
  7. WriteLock. lock ();
  8. Try {
  9. If (! Update ){
  10. // The data preparation process is omitted)
  11. Update = true;
  12. }
  13. ReadLock. lock ();
  14. } Finally {
  15. WriteLock. unlock ();
  16. }
  17. // The lock downgrade is complete, and the write lock is downgraded to the read lock
  18. }
  19. Try {
  20. // The data usage process is omitted)
  21. } Finally {
  22. ReadLock. unlock ();
  23. }
  24. }

In the above example, when the data changes, the update variable Boolean Type and Volatile modification) is set to false. At this time, all threads accessing the processData () method can perceive the changes, however, only one thread can obtain the write lock, and other threads will be blocked by the lock () method of the read lock and write lock. After data preparation is completed, the read lock is obtained and the write lock is released to downgrade the lock.

Is it necessary to obtain the read lock in lock downgrade? The answer is necessary. The main reason is to ensure data visibility. If the current thread does not get the read lock but directly releases the write lock, assuming another thread is recorded as thread T at the moment) it acquires the write lock and modifies the data, the current thread cannot perceive the data update of thread T. If the current thread obtains the read lock, that is, it follows the lock downgrade step, the thread T will be blocked until the current thread uses data and releases the read lock, the thread T can obtain the write lock for data update.

RentrantReadWriteLock does not support the process of upgrading the lock to hold the read lock, obtaining the write lock, and finally releasing the read lock ). The reason is to ensure data visibility. If the read lock has been obtained by multiple threads, and any thread has successfully obtained the write lock and updated the data, the update is invisible to other threads that have obtained the read lock.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.