Concurrent Notes Portal:
1.0 Concurrent Programming-Mind Map
2.0 Concurrent Programming-Thread Safety Fundamentals
3.0 Concurrent Programming-Basic Building Module
4.0 Concurrent Programming-Task Execution-Future
5.0 Concurrent Programming-Multi-threaded Performance and Scalability
6.0 Concurrent Programming- Explicit lock and synchronized
7.0 concurrent programming-AbstractQueuedSynchronizer
8.0 concurrent programming-atomic variables and non-blocking synchronization mechanism
Explicit lock
Before Java 5, the only mechanism that can be used when coordinating access to shared objects is
synchronized
sumvolatile
. Java 5 has been addedReentrantLock
.ReentrantLock
It is not a way to replace the built-in locking, but as an optional advanced function when the built-in locking mechanism is not applicable.
Lock 与 ReentrantLock
Lock provides an unconditional, pollable, timed, and interruptible acquisition operation. All locking and unlocking methods are explicit.
In the implementation of Lock, the same memory visibility semantics as internal locks must be provided, but they can be different in terms of locking semantics, scheduling algorithms, order guarantees, and performance characteristics.
package java.util.concurrent.locks;
/**
* @see ReentrantLock
* @see Condition
* @see ReadWriteLock
*
* @since 1.5
* @author Doug Lea
*/
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
ReentrantLock
Implements Lock
the interface, and provides the synchronized
visible memory and the same row of the mutex. And synchronized
like the same, ReentrantLock
it also provides 可重入(可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。)
locking semantics.
Why create a new locking mechanism that is so similar to memory locks?
In most cases, the built-in lock works well, but there are some limitations in functionality. For example, you cannot interrupt a thread that is waiting to acquire a lock, or you cannot wait indefinitely while requesting a lock.
The built-in lock must be released in the code block that acquired the lock, which simplifies the coding work and achieves a good interaction with exception handling operations, but it cannot implement the locking rules of the non-blocking structure. These are synchronized
the reasons for use , but in some cases, a more flexible locking mechanism can usually provide better liveness or performance.
To call Lock explicitly, the lock must be released in finally. Although it is not difficult to release the lock in finally, it may be forgotten.
Polling lock and time lock
The timed and pollable lock acquisition mode is
tryLock
realized by the method. Compared with the unconditional lock acquisition mode, it has a more complete error recovery mechanism.
Interruptible lock acquisition operation
lockInterruptibly
The method can maintain the response to the interrupt while obtaining the lock.
The code is below Log! ! ! ! !
The lock() method prints: After thread 1 cannot obtain the lock, it will wait for the release of the lock and will not respond to the interrupt. When thread 0 releases the lock, thread 1 resumes its response to the interrupt.
Thread-0:start get lock
Thread-0:already get lock
Thread-1:start get lock
Thread-0:working num 0
Thread-0:working num 1
Thread-0:working num 2
Thread-0:working num 3
Thread-0:working num 4
Thread-0:working num 5
Thread-0: release unlock
Thread-1:already get lock
Thread-1:Interrupt
Thread-1: release unlock
The lockInterruptibly() method prints: Thread 1 can respond to the interrupt in time after it cannot obtain the lock.
Thread-0:start get lock
Thread-0:already get lock
Thread-1:start get lock
Thread-1:Interrupt
Thread-1: unlock failed
Thread-1: failed desc:null
Thread-0:working num 0
Thread-0:working num 1
Thread-0:working num 2
Thread-0:working num 3
Thread-0:working num 4
Thread-0:working num 5
Thread-0: release unlock
Run the sample code lockInterruptibly
and lock
the difference will be clear .
public static void main(String[] args) throws InterruptedException {
LockTest lockTest = new LockTest();
Thread t0 = new Thread(new Runnable(){
@Override
public void run() {
lockTest.doWork();
}
});
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
lockTest.doWork();
}
});
// 启动线程t1
t0.start();
Thread.sleep(10);
// 启动线程t2
t1.start();
Thread.sleep(100);
// 线程t1没有得到锁,中断t1的等待
t1.interrupt();
}
class LockTest {
private Lock lock = new ReentrantLock();
public void doWork() {
String name = Thread.currentThread().getName();
try {
System.out.println(name + ":start get lock");
//lock.lock();
lock.lockInterruptibly();
System.out.println(name + ":already get lock");
for (int i = 0; i < 6; i++) {
Thread.sleep(1000);
System.out.println(name + ":working num "+ i);
}
} catch (InterruptedException e) {
System.out.println(name + ":Interrupt");
}finally{
try {
lock.unlock();
System.out.println(name + ": release unlock");
} catch (Exception e) {
System.out.println(name + ": unlock failed");
System.out.println(name + ": failed desc:" + e.getMessage());
}
}
}
}
Performance considerations
When Java 5 is added ReentrantLock
, it can provide better competitive performance than built-in locks. Java 6 uses an improved algorithm to manage built-in locks, so that built-in locks ReentrantLock
are almost the same in throughput, and their scalability is basically the same.
Fairness
There
ReentrantLock
are two fairness choices in the constructor: create an unfair lock (default) or a fair lock.
- Fair lock: Threads acquire locks in the order in which they make requests.
- Unfair lock: If the state of the lock becomes available while the thread is making a request, then the thread will skip all waiting threads and acquire the lock.
In a fair lock, if another thread is holding the lock or another thread is waiting for the lock in the queue, the newly requested thread will be put into the queue. In an unfair lock, only when the lock is held by a thread, the newly requested thread will be put into the queue.
Why don't we want all locks to be fair?
When performing locking operations, the overhead of using fair locks in suspending and resuming threads can greatly reduce performance. And in actual situations, statistical fairness guarantees (to ensure that the blocked thread can finally obtain the lock) are usually sufficient, and the overhead will be much smaller. Some rely on fair queuing algorithms to ensure the correctness of the business, but these algorithms are not common. In most cases, the performance of unfair locks is higher than that of fair locks.
In synchronized
and ReentrantLock
choose between
ReentrantLock
The semantics provided on locks and memory are the same as the built-in locks. In addition, it also provides some other functions: timing lock waiting, interruptible lock waiting, fairness, and non-block-structured locking.
Compared with explicit locks, built-in locks still have great advantages. The built-in lock is more familiar to developers, and is simple and compact. ReentrantLock
The danger is higher than the synchronization mechanism. If you forget finally
to call it in the block, unlock()
a time bomb has actually been planted.
In some cases where the built-in lock cannot meet the needs, it ReentrantLock
can be used as an advanced tool. It should be used when some advanced functions are needed ReentrantLock
. These functions include: timed, pollable and interruptible acquisition operations, fair queues, and non-block structure locks. Otherwise, use it first synchronized
.
Read-write lock
ReentrantLock
A standard mutex lock is implemented: at most one thread holds each timeReentrantLock
. But for maintaining the integrity of the data, mutual exclusion is usually an overly strong locking rule, which limits concurrency. Mutual exclusion is a conservative locking strategy. Although conflicts can be avoided写/写
,写/读
conflicts can also be avoided读/读
. Therefore, if读/读
the locking requirements of the situation are relaxed , the performance of the program will be improved. In this case, there is读-写锁
: a resource can be accessed by multiple read operations, or accessed by one write operation, but the two cannot be performed at the same time.
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
In 读-写锁
the locking strategy, multiple read operations are allowed at the same time, but only one write operation is allowed at a time.
ReentrantReadWriteLock
Provides important locking semantics for both types of locks. And ReentrantLock
similar,
ReentrantReadWriteLock
when construction can also choose to be a fair one non-lock (default) or a fair lock.
- In a fair lock, the thread with the longest waiting time will get the lock first. If this lock is held by a reader thread and another thread requests a write lock, no other reader threads can acquire the read lock until the writer thread runs out and releases the writer lock.
- In an unfair lock, the order in which threads obtain access permissions is uncertain. It is possible to downgrade a writer thread to a reader thread, but it is not possible to upgrade from a reader thread to a writer thread (this will cause a deadlock).
Example of read-write lock code:
public class ReadWriteMap<K,V>{
private final Map<K,V> map;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock read = lock.readLock();
private final Lock write = lock.writeLock();
public ReadWriteMap(Map<K,V> map){
this.map = map;
}
public V put(K key,V value){
write.lock();
try {
return map.put(key, value);
}finally{
write.unlock();
}
}
public V get(Object key){
read.lock();
try {
return map.get(key);
}finally{
read.unlock();
}
}
}
Compared with the built-in lock, the explicit Lock provides some extended functions and has higher flexibility. Flexibility, and have better control over queue lines. But it ReentrantLock
cannot be completely replaced synchronized
. synchronized
It should be used only when the demand cannot be met.
读-写锁
Allows multiple reader threads to concurrently access the protected object. When accessing data structures dominated by read operations, it can improve the scalability of the program.