1.lock流程图
2.unlock
unlock的流程相对简单,就是将state置为0
,将exclusiveOwnerThread置为NULL(不考虑重入不考虑失败)
,然后将队列中下一个节点唤醒。
3.代码
定义一个Lock接口
public interface Lock {
void lock();
void unlock();
}
实现Lock接口实现自定义锁
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.concurrent.locks.LockSupport;
/*
* @Author shstart
* @Date 2021-12-1
* */
public class MiniReentrantLock implements Lock {
/**
* 锁是什么? 资源 -> state
* state = 0 未加锁
* state > 0 当前lock是加锁状态
* 这里使用volatile修饰,保证内存的可见性。
*/
private volatile int state;
/**
* 独占模式? 同一时刻只能有一个线程可以持有锁,其他的线程,未获取到锁会被阻塞。
* 当前持有锁的线程。
*/
private Thread exclusiveOwnerThread;
/**
* 需要有两个引用去维护阻塞队列。
* head 指向队列的头结点 tail指向队列的尾结点
*/
private Node head; //head节点对应的线程 就是当前获取锁的线程
private Node tail; //尾结点
/**
* 阻塞的线程是要被唤醒的。
* 这里将阻塞的线程封装成一个Node节点,放到FIFO双向队列中。
*/
static class Node {
Node prev; //前置节点
Node next; //后置节点
Thread thread; //封装的线程本身。
public Node() {
}
public Node(Thread thread) {
this.thread = thread;
}
public Node(Thread thread, Node prev, Node next) {
this.thread = thread;
this.prev = prev;
this.next = next;
}
}
/**
* 获取锁
* 假设锁被占用,则会阻塞当前线程,直到抢占到锁为止
* <p>
* lock的过程:
* 情景1: 线程进来之后发现,当前state == 0, 这个时候就很幸运,直接抢锁
* 情景2: 线程进来之后发现,当前state > 0, 这个时候就需要将当前线程入队。
*/
@Override
public void lock() {
/*
* 第一次获取到锁时 state =1
* 第n次重入时 state = n
* */
acquire(1);
}
/**
* 竞争资源
* 1.尝试获取锁,成功则占用锁返回
* 2.抢占锁失败,阻塞当前线程
*/
private void acquire(int arg) {
//尝试获取锁失败
if (!tryAcquire(arg)) {
//入队
Node node = addWaiter();
//在队列中尝试获取锁
acquireQueued(node, arg);
}
}
/**
* 尝试抢占锁失败,需要做什么?
* 1.需要当前线程封装为node,加入到队列
* 2.需要将当前线程park(挂起),等待被唤醒
* <p>
* 唤醒后呢?
* 1.检查当前node节点是否为head.next
* (注意: head节点是已经获取了锁的节点,这时候队列中只有
* head.next这个节点才有资格去抢占锁,其他线程都没有抢占的权限)
* 2.抢占
* 成功: 将当前node设置为head,将老的head出队,返回到业务层面
* 失败 继续park,等待被唤醒
* <p>
* -----------
* 添加到阻塞队列中的逻辑 addWaiter()
* 在队列中竞争资源的逻辑 acquireQueued()
*/
private void acquireQueued(Node node, int arg) {
//只有当前node成功获取到锁后才会跳出自旋
for (; ; ) {
Node pred = node.prev;
//只有当前节点是当前节点是head.next 才能尝试获取锁
if (pred == head && tryAcquire(arg)) {
//设置头结点
setHead(node);
//help GC
pred.next = null;
//抢到锁 直接return;
return;
}
//不符合抢锁条件 挂起当前线程
System.out.println("线程: " + Thread.currentThread().getName() + " .挂起");
LockSupport.park();
System.out.println("线程: " + Thread.currentThread().getName() + " .唤醒");
//什么时候唤醒被park的线程呢? unlock过程
}
}
/*
* 将当前线程入队,保证当前线程已经入队成功
* */
private Node addWaiter() {
Thread thread = Thread.currentThread();
Node node = new Node(thread);
/*
* 如何入队 ?
* 1.找到node的前置节点 x
* 2.更新node.prev = x;
* 3.CAS更新tail 为 node
* 4.更新x.next = node;
* */
//队列中有元素
Node x = tail;
if (x != null) {
node.prev = x;
if (compareAndSetTail(x, node)) {
x.next = node;
return node;
}
}
/**
* 执行到这里有几种情况?
* 1.tail == null 队列是空队列
* 2.CAS设置node为tail时失败,存在并发
*/
//自旋入队(一定会成功入队)
enq(node);
return node;
}
/**
* 自旋入队 只有成功入队后才返回
**/
private void enq(Node node) {
for (; ; ) {
/**
* >> 第一种情况:队列是空队列
* >> 当前线程是第一个抢占锁失败的线程
* 因为第一个获取锁的线程a直接CAS成功时不会构造node,所以当前线程作为a的第一个后驱线程,
* 需要在队列中补充一个node作为head节点,(head节点任何时候都表示当前获取锁的线程)
* 下次自旋才会去正在的添加节点
**/
if (tail == null) {
//补充节点成功 (这里的期望值是NULL)
if (compareAndSetHead(new Node())) {
tail = head;
}
} else {
//当前队列中刚已经有node了,需要CAS方式追加一个node
Node x = tail;
if (x != null) {
node.prev = x;
//CAS设置成功。
if (compareAndSetTail(x, node)) {
x.next = node;
//入队成功后直接return。
return;
}
}
}
}
}
/**
* 尝试获取锁
* true -> 抢占成功
* false -> 抢占失败
*/
private boolean tryAcquire(int arg) {
if (state == 0) {
/**
* 当前state == 0,是否可以直接抢锁呢? 不可以。
* 这里模拟的是一个公平锁,首先得判断同步队列中是否有节点,有节点的话这里必须要先排队。
* 所以这里必须判断队列中是否有节点,没有节点的话才能试图去抢占锁。如果有节点直接失败
* 通过CAS的方式去抢占锁,因为同一时刻可能有很多线程都在抢占锁。
* */
if (!hasQueuedPredecessor() && compareAndSetState(0, arg)) {
//走到这里说明抢占锁成功了,需要将exclusiveOwnerThread设置为当前线程。
this.exclusiveOwnerThread = Thread.currentThread();
return true;
}
/**
* 判断当前线程就是持有锁的线程,所以这里就是重入的逻辑
* */
} else if (Thread.currentThread() == this.exclusiveOwnerThread) {
//计算state的下一个状态。
int c = getState() + arg;
/**
* 这里设置state不需要加锁,因为当前线程就是加锁的线程
* 即这里不存在并发
*/
this.state = c;
return true;
}
/**
* 什么时候会加锁失败?
* 1.state = 0 && (队列中有等待的线程 || CAS设置state失败(有并发))
* 2.(state > 0 && 当前线程不是获取锁的线程)
* */
return false;
}
/**
* true -> 表示当前线程前面有等待者线程
* false -> 当前线程前没有其他等待者线程
*/
private boolean hasQueuedPredecessor() {
Node h = head;
Node t = tail;
Node s;
return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}
/**
* 释放锁
*/
@Override
public void unlock() {
release(1);
}
public void release(int arg) {
//条件成立 说明线程已经完全释放锁了,
if (tryRelease(arg)) {
Node head = this.head;
//唤醒head.next
if (head.next != null) {
unparkSuccessor(head);
}
}
}
//唤醒节点
private void unparkSuccessor(Node node) {
Node s = node.next;
if (s != null && s.thread != null) {
LockSupport.unpark(s.thread);
}
}
/*
* 完全释放成功,返回true
* 否则说明state > 0, 返回false。
* */
public boolean tryRelease(int arg) {
int c = getState() - arg;
//当先线程不是获取锁的线程 直接抛出异常
if (getExclusiveOwnerThread() != Thread.currentThread()) {
throw new RuntimeException("fuck you !!! must getLock");
}
//如果执行到这里存在并发吗? 不存在 只有一个线程(当前持有锁的线程)会来到这里
//c == 0,说明状态已经被完全释放了,
if (c == 0) {
//独占线程置为NULL
this.exclusiveOwnerThread = null;
//状态设置为0
this.state = 0;
return true;
}
//当前线程没有完全释放锁 (c != 0)
this.state = c;
return false;
}
/**
* 设置队列的新头结点
*/
private void setHead(Node node) {
this.head = node;
//因为当前节点已经是获取锁的线程了,直接将引用的thread属性置为NULL即可
node.thread = null;
//将前置节点也置为NULL。
node.prev = null;
}
/**
* 基于CAS,得到state、head、tail属性的setter方法为其赋值
*/
private static final Unsafe unsafe;
private static final long stateOffset;
private static final long headOffset;
private static final long tailOffset;
static {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe) f.get(null);
stateOffset = unsafe.objectFieldOffset
(MiniReentrantLock.class.getDeclaredField("state"));
headOffset = unsafe.objectFieldOffset
(MiniReentrantLock.class.getDeclaredField("head"));
tailOffset = unsafe.objectFieldOffset
(MiniReentrantLock.class.getDeclaredField("tail"));
} catch (Exception ex) {
throw new Error(ex);
}
}
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
public int getState() {
return state;
}
public Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
public Node getHead() {
return head;
}
public Node getTail() {
return tail;
}
}