The last tool in the lock is introduced from this section: read-write Lock (Readwritelock).
Reentrantlock implements a standard mutex, which is the notion that only one thread can hold a lock at a time, or the so-called exclusive lock. This feature has been emphasized in the previous chapters. Obviously this feature reduces throughput to a certain extent, in fact exclusive locks are a conservative locking strategy, in which case any "read/read", "Write/read", "Write/write" operations cannot occur simultaneously. However, it is also important to emphasize that the lock has a certain overhead, when the concurrency is relatively large, the cost of the lock is more objective. So if possible, try to use less lock, if you want to use the lock, you can change to read and write lock.
Readwritelock describes a resource that can be accessed by multiple read threads, or accessed by a write thread, but not concurrently with a read-write thread. In other words, the use of read-write locks is a shared resource is a large number of read operations, and only a small number of write operations (modify data). Listing 1 describes the API for Readwritelock.
Listing 1 Readwritelock interface
Public interface Readwritelock {
Lock Readlock ();
Lock Writelock ();
}
The readwritelock structure described in Listing 1 shows that Readwritelock is not a sub-interface of lock, except that Readwritelock uses lock to read and write two views. A read lock is required every time the shared data is read in the Readwritelock, and a lock is required when the shared data needs to be modified. It looks like two locks, but not really, the mystery is explained in the analysis in the next section.
The implementation of Readwritelock in JDK 6 is reentrantreadwritelock.
Listing 2 Simpleconcurrentmap
Package xylz.study.concurrency.lock;
Import java.util.ArrayList;
Import java.util.Collection;
Import Java.util.HashSet;
Import Java.util.Map;
Import Java.util.Set;
Import Java.util.concurrent.locks.Lock;
Import Java.util.concurrent.locks.ReadWriteLock;
Import Java.util.concurrent.locks.ReentrantReadWriteLock;
public class Simpleconcurrentmap<k, v> implements Map<k, v> {
Final Readwritelock lock = new Reentrantreadwritelock ();
Final Lock r = Lock.readlock ();
Final Lock w = Lock.writelock ();
Final map<k, v> Map;
Public Simpleconcurrentmap (map<k, v> Map) {
This.map = map;
if (map = = null) throw new NullPointerException ();
}
public void Clear () {
W.lock ();
try {
Map.clear ();
} finally {
W.unlock ();
}
}
public boolean ContainsKey (Object key) {
R.lock ();
try {
return Map.containskey (key);
} finally {
R.unlock ();
}
}
public boolean Containsvalue (Object value) {
R.lock ();
try {
return Map.containsvalue (value);
} finally {
R.unlock ();
}
}
Public set<java.util.map.entry<k, V>> EntrySet () {
throw new Unsupportedoperationexception ();
}
Public V get (Object key) {
R.lock ();
try {
return Map.get (key);
} finally {
R.unlock ();
}
}
public Boolean isEmpty () {
R.lock ();
try {
return Map.isempty ();
} finally {
R.unlock ();
}
}
Public set<k> KeySet () {
R.lock ();
try {
return new Hashset<k> (Map.keyset ());
} finally {
R.unlock ();
}
}
Public V put (K key, V value) {
W.lock ();
try {
Return Map.put (key, value);
} finally {
W.unlock ();
}
}
public void Putall (map<? extends K,? extends v> m) {
W.lock ();
try {
Map.putall (m);
} finally {
W.unlock ();
}
}
Public V Remove (Object key) {
W.lock ();
try {
return Map.Remove (key);
} finally {
W.unlock ();
}
}
public int size () {
R.lock ();
try {
return Map.size ();
} finally {
R.unlock ();
}
}
Public collection<v> values () {
R.lock ();
try {
return new Arraylist<v> (Map.values ());
} finally {
R.unlock ();
}
}
}
Listing 2 describes a thread-safe map implemented with a read-write lock. What needs to be specifically stated is that the EntrySet () method is not implemented, because the implementation of this method is more complex and the details are discussed in detail in the later chapters of Concurrenthashmap. Also here keyset () and values () do not directly return to the map view, but a new view mapping the original elements, in fact, this entryset () is to protect the data logic of the original map, prevent incorrect modification caused the original map data error. In particular, it is not necessary to write a thread-safe map implementation in Listing 2 without special needs, as Concurrenthashmap has already done so.
Readwritelock requires a strict distinction between read and write operations, and if the read operation uses a write lock, the throughput of the read operation is reduced, and if the write operation uses a read lock, data errors can occur.
In addition, Reentrantreadwritelock also has the following features:
- Fair sex
- Unfair Lock (default) This is the same as the non-fairness of the exclusive lock, because there is no lock contention between the read threads, so there is no fairness or unfairness in the read operation, and one or more read operations or writes are deferred because the write operation may acquire the lock immediately. Therefore, the throughput of the unfair lock is higher than the fair lock.
- Fair Lock takes advantage of the Aqs's CLH queue, releasing the currently held lock (read or write), prioritizing the write lock for the thread that is waiting for the longest time to wait, as long as the write thread waits longer than the wait time for all the read threads. The same thread holds the write lock or a write thread is already waiting, so all threads (including read-write threads) that attempt to get a fair lock are blocked until the first write thread releases the lock. If the waiting time for the read thread is longer than the write thread's wait time, the set of read threads acquires the lock once the previous write thread releases the lock.
- Re-entry Sex
- Read-write locks allow both the reading and writing threads to regain read or write locks in the order in which they are requested. Of course, only the write thread releases the lock, and the read thread can get the re-entry lock.
- The write thread acquires the write lock and can acquire the read lock again, but the read lock cannot acquire the write lock after the reading thread acquires it.
- In addition, read-write locks support up to 65,535 recursive write locks and 65,535 recursive read locks.
- Lock downgrade
- After the write thread acquires the write lock, it can acquire a read lock and then release the write lock, which changes from a write lock to a read lock to achieve the feature of lock demotion.
- Lock escalation
- Read locks cannot be directly promoted to write locks. Because acquiring a write lock requires all read locks to be freed, a deadlock occurs if there are two read lock views that acquire a write lock without releasing the read lock.
- Lock get Interrupt
- Both the read lock and the write lock support the acquisition lock during the outage. This is consistent with the exclusive lock.
- Condition variable
- The write lock provides support for the conditional variable (Condition), which is consistent with the exclusive lock, but the read lock does not allow the conditional variable to get an
UnsupportedOperationException
exception.
- Re-entry number
- The maximum number of read and write locks is only 65535 (including the number of reentrant). This is described in the next section.
The above features are useful for understanding read-write locks and are necessary, as well as the Readwritelock implementations in the next section.
In layman's Java Concurrency (13): Lock mechanism Part 8 read-write Lock (Reentrantreadwritelock) (1) [Turn]