浅出重入锁reentrantlock
下面简单演示一下 使用JDK的JUC并发包的reentrantlock
public class MyReentrantLock {
// 锁是什么?
// 锁是用来解决 多线程并发访问带来的资源竞争 引发的共享资源的安全性问题
// 对一个共享资源加锁之后 如果一个线程获得了锁 那么其他线程不能访问这个资源
public static Lock lock = new ReentrantLock();
public static int count = 0;
public static void incr() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
try{
lock.lock();
count+=2;
// 在调用其他加该锁的方法 不需要再去争抢锁 只需要记录重入的次数
decr();
System.out.println("-->"+count);
}finally {
lock.unlock();
}
}
public static void decr(){
try {
lock.lock();
count--;
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
new Thread(MyReentrantLock::incr).start();
}
Thread.sleep(2000);
System.out.println(count);
}
}
之前我们使用synchronized和volatile来保证结果得到1000,这次使用reentrantlock看起俩比较简洁
后面的各种锁的核心就是AQS,那到底什么是AQS,下面我们来一起看看源码
第一步看看lock()方法对应有两种实现方式:一种是公平锁 另一种是非公平锁
下面来分析一下非公平锁实现机制,在第一个线程进入compareAndSetState(期望值:0,更新成1),意思就是如果期望值是0那么就更新成1,然后后续的线程就跟期望值不相等,走下面的acquire(1);
这个互斥变量起着重要作用,0表示无锁状态,大于0表示有锁状态
在返回true之后,表示这个线程已经拿到这个锁,然后把当前线程保存到exclusiveOwnerThread;
后续的线程会返回false,继续acquire()
接下里分析acquire(),然后进入tryAcquire()里面看看
其他线程继续走acquireQueued(addWaiter(Node.EXCLUSIVE), arg),接下来就看里面的一个addWaiter(Node.EXCLUSIVE)
然后进入enq(node)看看,初始化Head 和 Tail结点都是null,然后把当前线程放进双向链表结点取代为空的Tail结点;
当下一个线程来就会走上图当中的enq(node)上面的逻辑,把下一个线程放入双向Node,这个算法比较简单刷过LeetCode基本一下就能看懂->双向链表
回到之前继续走acquireQueued(addWaiter(Node.EXCLUSIVE), arg),进入acquireQueued看看
然后进入shouldParkAfterFailedAcquire(p, node)里面看看,挂起自己之前,需要将前置节点的 ws 状态设置成 SIGNAL,告诉他:你释放锁的时候记得唤醒我
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
// 如果他的上一个节点的 ws 是 SIGNAL,他就需要阻塞。
if (ws == Node.SIGNAL)
// 阻塞
return true;
// 前任被取消。 跳过前任并重试。
if (ws > 0) {
do {
// 将前任的前任 赋值给 当前的前任
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
// 将前任的前任的 next 赋值为 当前节点
pred.next = node;
} else {
// 如果没有取消 || 0 || CONDITION || PROPAGATE,那么就将前任的 ws 设置成 SIGNAL.
// 为什么必须是 SIGNAL 呢?
// 答:希望自己的上一个节点在释放锁的时候,通知自己(让自己获取锁)
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
// 重来
return false;
}
!!!看JDK源码其实不是很难,这个比spring源码简单多了 简书的相关文章