Table of contents
1. Lock class hierarchy and related API
1. Lock class hierarchy
Both ReentrantLock and ReentrantReadWriteLock are java.util.concurrent
tool classes under the concurrent package. ReentrantLock implements the Lock interface, ReentrantReadWriteLock implements the ReadWriteLock interface, and ReadLock and WriteLock implement the Lock interface.
2. API related to Lock interface
In order to distinguish between synchronized and ReentrantLock, let's first understand the APIs related to the Lock interface.
Conclusion:
1. lock() is the most commonly used.
2. The lockInterruptibly() method is generally more expensive. Some impls may not implement lockInterruptibly, and it is only used when it really needs to respond to interrupts.
3. About Condition
wait()
The , notify()
, notifyAll()
and in Object synchronized
can only be used in conjunction with keywords to wake up one or all threads. Condition needs to be used in conjunction with Lock to provide multiple waiting sets and more precise control.
Remarks: If Lock replaces the unlocking logic of the synchronization code block or synchronization method, then Condition replaces the waiting and wake-up logic of Object.
二、synchronized VS Lock
1. Advantages and disadvantages of synchronized locks
Let's first talk about the advantages of the platform-level lock implemented by synchronized:
- Simple to use and clear semantics, just add the synchronized keyword to the method or use a synchronized code block.
- Provided by the JVM, it provides a variety of optimization solutions, such as lock coarsening, lock elimination, biased locks, lightweight locks, heavyweight locks, etc. For these optimization features, please refer to: The principle of the synchronized keyword in Java interview questions and lock related .
- The release of the lock is done by the JVM without manual intervention, which also reduces the possibility of deadlock.
Let me talk about its shortcomings:
some advanced functions of locks cannot be realized, such as 超时锁
, 中断锁
, 读写锁
, 共享锁
, 公平锁
.
2. The advantages and disadvantages of Lock implementation
The lock implemented by Lock mainly makes up for the shortcomings of synchronized, such as the advanced functions of the lock mentioned above, such as 超时锁
, 中断锁
, 读写锁
, 共享锁
, and 公平锁
these.
Let me talk about its disadvantages:
- The lock needs to be released manually, and improper use by novices may cause deadlock.
- There are not as many optimization items as the lock implemented by synchronized.
3. A simple ReentrantLock
1. ReentrantLock implements a simple process
First introduce some key attributes, as follows:
waiters
It represents the lock pool. To put it bluntly, it is the waiting queue for threads that fail to grab locks.owner
Represents the thread that successfully acquired the lock.count
The reentrant count used to mark the lock.
First describe the locking process:
- If the number of reentrants is 0, it means that the lock has not been held by any thread. At this time,
CAS(0, count + 1)
the operation can be used to grab the lock. - If the number of reentrants is not 0, it is judged whether the thread currently grabbing the lock is the thread holding the lock, and if so, the number of reentrants is increased by 1 .
- If the above two conditions are not met, it will directly count as a lock grab failure.
- Threads that fail to grab the lock directly enter the waiting queue and block waiting.
Then describe the unlocking process:
- Throw if the thread calling the unlock method is not the thread holding the lock, otherwise
IllegalMonitorStateException
step 2. - The number of reentrants -1 , if the number of reentrants is 0, it means the unlocking is successful .
- After the unlock is successful, wake up the lock-grabbing thread waiting at the head of the queue.
2. Code example
public class NicksReentrantLock implements Lock {
// 用来标识哪个线程获取到锁
private Thread owner;
// 重入次数
private AtomicInteger counter = new AtomicInteger(0);
// 等待队列
private BlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();
@Override
public void lock() {
if (!tryLock()) {
waiters.offer(Thread.currentThread());
// 循环解决伪唤醒问题
while (true) {
// 如果队列头部是当前线程说明可以抢锁
if (Thread.currentThread() == waiters.peek()) {
// 若抢锁成功则出队列
if (tryLock()) {
waiters.poll();
return;
}
}
// 若当前线程不在队列头部或者抢锁失败则挂起
LockSupport.park();
}
}
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
int ct = counter.get();
if (ct == 0) {
// 重入次数为0说明当前线程可以通过CAS操作获取锁
if (counter.compareAndSet(0, ct + 1)) {
owner = Thread.currentThread();
return true;
}
return false;
}
// 不为0则判断获取锁的线程是否是当前线程,如果是当前线程,则将可重入次数加1
if (owner == Thread.currentThread()) {
counter.set(ct + 1);
return true;
}
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void unlock() {
// 解锁成功应该唤醒队列头部的线程
if (tryUnlock()) {
Optional.ofNullable(waiters.peek()).ifPresent(LockSupport::unpark);
}
}
public boolean tryUnlock() {
// 如果当前解锁的线程不是获取到锁的线程则抛异常
if (Thread.currentThread() != owner) {
throw new IllegalMonitorStateException();
}
int ct = counter.get();
int nextCt = ct - 1;
counter.set(nextCt);
if (nextCt == 0) {
owner = null;
return true;
}
return false;
}
@Override
public Condition newCondition() {
return null;
}
}
3. Test cases
public class Test {
private static int count = 0;
public static void main(String[] args) {
NicksReentrantLock lock = new NicksReentrantLock();
for (int index = 0; index < 10000; index++) {
new Thread(() -> {
try {
lock.lock();
count++;
} finally {
lock.unlock();
}
}).start();
}
LockSupport.parkNanos(1000 * 1000 * 1000);
System.out.println("累加后的值为:" + count);
}
}
Note: The console output is:
累加后的值为:10000
.