文章目录
- 什么是非公平锁和公平锁呢?
- 我们来看看acquire(1)的源码如下:
- 这里的判断条件主要做两件事:
- 在`tryAcquire()`方法中,主要是做了以下几件事:
- 公平锁的`tryAcquire()`,实现的原理图如下:
- 我们来看看`acquireQueued()`方法,
- `acquireQueued()`方法主要执行以下几件事:
- 在`lock()`方法,其实在lock()方法中,已经包含了两方面:
- 总结上面的几个方法,unlock释放锁方法的执行原理图如下:
- 如下面的非公平锁,尝试获取锁`nonfairTryAcquire()`源码如下:
- `lock`锁的核心的`Api`如下:
- 最后的属性为当前锁的拥有者,直接就用`Thread`来封装,定义如下:
- 然后是`trylock`方法,依据上面的源码分析,在`trylock`中主要执行的以下几件事:
完整的Java锁机制可以看这篇文章: https://blog.csdn.net/weixin_44797327/article/details/134761807?spm=1001.2014.3001.5502
什么是非公平锁和公平锁呢?
非公平锁就是不按照线程先来后到的时间顺序进行竞争锁,后到的线程也能够获取到锁,公平锁就是按照线程先来后到的顺序进行获取锁,后到的线程只能等前面的线程都获取锁完毕才执行获取锁的操作,执行有序。
我们来看看lock()这个方法,这个有区分公平锁和非公平锁,这个两者的实现不同,先来看看公平锁,源码如下:
// 直接调用 acquire(1)
final void lock() {acquire(1);}
我们来看看acquire(1)的源码如下:
public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}
这里的判断条件主要做两件事:
- 通关过该方法
tryAcquire(arg)
尝试的获取锁 - 若是没有获取到锁,通过该方法
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
就将当前的线程加入到存储等待线程的队列中。
其中tryAcquire(arg)
是尝试获取锁,这个方法是公平锁的核心之一,它的源码如下:
protected final boolean tryAcquire(int acquires) {// 获取当前线程 final Thread current = Thread.currentThread();// 获取当前线程拥有着的状态int c = getState();// 若为0,说明当前线程拥有着已经释放锁if (c == 0) {// 判断线程队列中是否有,排在前面的线程等待着锁,若是没有设置线程的状态为1。if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {// 设置线程的拥有着为当前线程setExclusiveOwnerThread(current);return true;}// 若是当前的线程的锁的拥有者就是当前线程,可重入锁} else if (current == getExclusiveOwnerThread()) {// 执行状态值+1int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");// 设置status的值为nextcsetState(nextc);return true;}return false;}
在tryAcquire()
方法中,主要是做了以下几件事:
- 判断当前线程的锁的拥有者的状态值是否为0,若为0,通过该方法
hasQueuedPredecessors()
再判断等待线程队列中,是否存在排在前面的线程。 - 若是没有通过该方法
compareAndSetState(0, acquires)
设置当前的线程状态为1。 - 将线程拥有者设为当前线程
setExclusiveOwnerThread(current)
- 若是当前线程的锁的拥有者的状态值不为0,说明当前的锁已经被占用,通过
current == getExclusiveOwnerThread()
判断锁的拥有者的线程,是否为当前线程,实现锁的可重入。 - 若是当前线程将线程的状态值+1,并更新状态值。
公平锁的tryAcquire()
,实现的原理图如下:
我们来看看acquireQueued()
方法,
该方法是将线程加入等待的线程队列中,源码如下:
final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;// 死循环处理for (;;) {// 获取前置线程节点final Node p = node.predecessor();// 这里又尝试的去获取锁if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;// 直接return interruptedreturn interrupted;}// 在获取锁失败后,应该将线程Park(暂停)if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}
acquireQueued()
方法主要执行以下几件事:
- 死循环处理等待线程中的前置节点,并尝试获取锁,若是
p == head && tryAcquire(arg)
,则跳出循环,即获取锁成功。 - 若是获取锁不成功
shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt()
就会将线程暂停。
在acquire(int arg)
方法中,最后若是条件成立,执行下面的源码:
selfInterrupt();
// 实际执行的代码为
Thread.currentThread().interrupt();
即尝试获取锁失败,就会将锁加入等待的线程队列中,并让线程处于中断等待。公平锁lock()
方法执行的原理图如下:
之所以画这些原理的的原因,是为后面写一个自己的锁做铺垫,因为你要实现和前人差不多的东西,你必须了解该东西执行的步骤,最后得出的结果,执行的过程是怎么样的。
有了流程图,在后面的实现自己的东西才能一步一步的进行。这也是阅读源码的必要之一。
在lock()
方法,其实在lock()方法中,已经包含了两方面:
- 锁方法
lock()
。 - 尝试获取锁方法
tryAquire()
。
接下来,我们来看一下unlock()方法的源码。
public void unlock() {sync.release(1);}
直接调用release(1)
方法,来看release
方法源码如下:
public final boolean release(int arg) {// 尝试释放当前节点if (tryRelease(arg)) {// 取出头节点Node h = head;if (h != null && h.waitStatus != 0)// 释放锁后要即使唤醒等待的线程来获取锁unparkSuccessor(h);return true;}return false;}
通过调用tryRelease(arg)
,尝试释放当前节点,若是释放锁成功,就会获取的等待队列中的头节点,就会即使唤醒等待队列中的等待线程来获取锁。接下来看看tryRelease(arg)
的源码如下:
// 尝试释放锁protected final boolean tryRelease(int releases) {// 将当前状态值-1int c = getState() - releases;// 判断当前线程是否是锁的拥有者,若不是直接抛出异常,非法操作,直接一点的解释就是,你都没有拥有锁,还来释放锁,这不是骗人的嘛if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;//执行释放锁操作 1.若状态值=0 2.将当前的锁的拥有者设为nullif (c == 0) {free = true;setExclusiveOwnerThread(null);}// 重新更新status的状态值setState(c);return free;}
总结上面的几个方法,unlock释放锁方法的执行原理图如下:
对于非公平锁与公平锁的区别,在非公平锁尝试获取锁中不会执行hasQueuedPredecessors()
去判断是否队列中还有等待的前置节点线程。
如下面的非公平锁,尝试获取锁nonfairTryAcquire()
源码如下:
final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {// 直接就将status-1,并不会判断是否还有前置线程在等待if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}
以上就是公平锁和非公平锁的主要的核心方法的源码,接下来我们实现自己的一个锁,首先依据前面的分析中,要实现自己的锁,拥有的锁的核心属性如下:
- 状态值
status
,0为未占用锁,1未占用锁,并且是线程安全的。 - 等待线程队列,用于存放获取锁的等待线程。
- 当前线程的拥有者。
lock
锁的核心的Api
如下:
lock
方法trylock
方法unlock
方法
依据以上的核心思想来实现自己的锁,首先定义状态值status,使用的是AtomicInteger
原子变量来存放状态值,实现该状态值的并发安全和可见性。定义如下:
// 线程的状态 0表示当前没有线程占用 1表示有线程占用AtomicInteger status =new AtomicInteger();
接下来定义等待线程队列,使用LinkedBlockingQueue
队列来装线程,定义如下:
// 等待的线程
LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<Thread>();
最后的属性为当前锁的拥有者,直接就用Thread
来封装,定义如下:
// 当前线程拥有者
Thread ownerThread =null;
接下来定义lock()
方法,依据上面的源码分析,在lock
方法中主要执行的几件事如下:
- 死循环的处理等待线程队列中的线程,知道获取锁成功,将该线程从队列中删除,跳出循环。
- 获取锁不成功,线程处于暂停等待。
@Overridepublic void lock() {// TODO Auto-generated method stub// 尝试获取锁if (!tryLock()) {// 获取锁失败,将锁加入等待的队列中waitersQueue.add(Thread.currentThread());// 死循环处理队列中的锁,不断的获取锁for (;;) {if (tryLock()) {// 直到获取锁成功,将该线程从等待队列中删除waitersQueue.poll();// 直接返回return;} else {// 获取锁不成功,就直接暂停等待。LockSupport.park();}}}}
然后是trylock
方法,依据上面的源码分析,在trylock
中主要执行的以下几件事:
- 判断当前拥有锁的线程的状态是否为0,为0,执行状态值+1,并将当前线程设置为锁拥有者。
- 实现锁可重入
@Overridepublic boolean tryLock() {// 判断是否有现成占用if (status.get()==0) {// 执行状态值加1if (status.compareAndSet(0, 1)) {// 将当前线程设置为锁拥有者ownerThread = Thread.currentThread();return true;} else if(ownerThread==Thread.currentThread()) {// 实现锁可重入status.set(status.get()+1);}}return false;}
最后就是unlock方法,依据上面的源码分析,在unlock中主要执行的事情如下:
- 判断当前线程是否是锁拥有者,若不是直接抛出异常。
- 判断状态值是否为0,并将锁拥有者清空,唤醒等待的线程。
@Overridepublic void unlock() {// TODO Auto-generated method stub// 判断当前线程是否是锁拥有者if (ownerThread!=Thread.currentThread()) {throw new RuntimeException("非法操作");}// 判断状态值是否为0if (status.decrementAndGet()==0) {// 清空锁拥有着ownerThread = null;// 从等待队列中获取前置线程Thread t = waitersQueue.peek();if (t!=null) {// 并立即唤醒该线程LockSupport.unpark(t);}}}