好了,在之前的文章。我们手写过一个Lock锁了,我们从锁的运行机制和原理可以认为锁是对synchronize关键字的一个增强。
那么我们来看看在Java中的JUC包中的ReentrantLock类,它是如何处理锁的?跟我们手写的Lock锁区别在什么地方。
首先我们来看看Lock接口提供的方法。
其中:
- lockInterruptibly() :如果当前线程被中断了,抛出异常。如果没有就尝试的拿锁,如果拿不动就进入循环等待。
- tryLock():如果获取到状态是可以拿锁的,使用CAS进行尝试拿锁,拿到后当前线程为持有锁线程。如果已经是拿锁线程再次tryLock那么会进行状态+1,即重入锁。
- newCondition():这个是返回一个condition对象。指当前锁的对象。
从方法设计上来看,跟我们手写的Lock锁本意无太大出入。但要关注的一个问题是,在ReentrantLock的实现上采用了一个叫AQS模型。
ReentrantLock
我们首先了解一个知识点,在并发设计中争夺锁的时候,线程会有哪些方式去争夺。
公平方式:使用该方式会忽略等待线程进入队列的位置,当有锁被释放时,所有等待线程都可以去争夺锁。
非公平方式:使用该方式会要求等待线程按先进队先获取的方式争夺锁。
使用RentrantLock
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class UsedReentrantLockTest {
private static final ReentrantLock lock = new ReentrantLock();
public void doProcessor() {
lock.lock();
try {
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+"已经拿到锁 执行任务中");
// 执行任务
Random random = new Random();
TimeUnit.SECONDS.sleep(random.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
UsedReentrantLockTest usedReentrantLockTest = new UsedReentrantLockTest();
new Thread(usedReentrantLockTest::doProcessor, "线程-1").start();
new Thread(usedReentrantLockTest::doProcessor, "线程-2").start();
new Thread(usedReentrantLockTest::doProcessor, "线程-3").start();
}
}
加锁
我们使用非公平方式来分析一下ReentrantLock的加锁机制。先看图
其中非公平的争夺锁的体现于,队列内的Node节点按照入队顺序循环获取锁的步骤。
解锁机制
AQS(AbstractQueuedSynchronizer)
AQS是整个并发锁机制的核心,大致的设计结构图如下:
这个是一个简易版本设计图,实际上原理的设计图会更加细致与巧妙。具体的分析文章再出一篇。
我们先初步了解一下这个AQS是怎么运行以及加锁的处理。
执行步骤:
- 当state!=0时, 当前线程被一个Node对象接收,如果存在头部节点下将当前线程添加到队列尾部,其中 prev指向上一个节点。没有头部节点,该线程就是头部节点。
- 当state==0时,当前线程进行CAS操作,尝试将state=1。如果成功当前线程被当做持有锁线程。如果失败将推出本次竞争加入到队列中循环等待,尝试下一次获取锁。
- 当state==0且当前线程已经持有锁,该线程的state+1。
大致情况是这样,如果需要更详细的了解可以阅读源码。