手写一个模拟的ReentrantLock(手写模块)

  本篇文章为你整理了手写一个模拟的ReentrantLock(手写模块)的详细内容,包含有怎么模拟手写 手写模块 手写模拟器demo 模拟手写输入法 手写一个模拟的ReentrantLock,希望能帮助你了解 手写一个模拟的ReentrantLock。

   * 情景1.线程进来后发现,当前state == 0 -- 直接去抢锁

   * 情景2.线程进来后发现,当前state 0 -- 将当前线程入队

   @Override

   public void lock() {

   // 第一次获取到锁时,将state设置为1

   // 第n次重入时,将state设置为n

   acquire(1);

   @Override

   public void unlock() {

   release(1);

   private void release(int arg) {

   // 条件成立:说明线程已经完全释放锁了

   if (tryRelease(arg)) {

   // 阻塞队列里面,还有睡觉的线程,应该唤醒一个线程

   // 首先需要知道有没有等待的node -- head.next == null

   Node head = this.head;

   if (head.nx != null) {

   // 公平锁,唤醒head.nx节点

   unparkSuccessor(head);

   private void unparkSuccessor(Node node) {

   Node s = node.nx;

   if (s != null s.thread != null) {

   LockSupport.unpark(s.thread);

   * 完全释放锁成功则返回true

   private boolean tryRelease(int arg) {

   int c = getState() - arg;

   if (getExclusiveOwnerThread() != Thread.currentThread()) {

   throw new RuntimeException("must get lock first");

   // 如果执行到这里,不存在并发,只会有一个线程会来到这里

   // 条件成立,则说明当前线程持有的lock锁已经完全释放了

   if (c == 0) {

   this.exclusiveOwnerThread = null;

   this.state = c;

   return true;

   } else {

   this.state = c;

   return false;

   * 竞争资源

   * 1.尝试获取锁。成功则占用锁,且返回

   * 2.抢占锁失败,阻塞当前线程

   * @param arg

   private void acquire(int arg) {

   if (!tryAcquire(arg)) {

   // 抢锁失败

   // step1.将当前线程封装成node,加入到阻塞队列中

   Node node = addWaiter();

   // step2.将当前线程park,使线程处于挂起状态

   acquireQueued(node, arg);

   // 抢锁成功

   // 1.抢到了锁

   // 2.重入了锁

   * 尝试抢锁失败,需要做的事:

   * 1.需要将当前线程封装成node,加入到阻塞队列中

   * 2.需要将当前线程park,使线程处于挂起状态

   * 唤醒的流程:

   * 1.检查当前node是否为head.next节点

   * head.next是拥有抢占权限的线程,其它node都没有抢占的权限

   * 2.抢占:

   * 成功:

   * 1.将当前node设置为node,将老的head出队,返回到业务层面

   * 2.继续park等待被唤醒

   * ----------------------------------------------

   * 1.添加到阻塞队列的逻辑 addWaiter()

   * 2.竞争资源的逻辑 acquireQueued()

   private void acquireQueued(Node node, int arg) {

   // 当前线程已经放到queue中了

   // 只有当前node成功获取到锁以后才会跳出自旋

   for (; ; ) {

   // 什么情况下,当前node被唤醒后可以尝试去获取锁呢?

   // 只有一种情况,当前node是head的后继节点,才有这个权限

   // 不是就先来后到

   Node pvNode = node.pv;

   // 条件1:pvNode == head

   // true -- 说明当前node拥有抢占权限

   // queue中的第一个节点代表的是当前锁正在执行的线程 -- head指向的线程

   // head后面的线程代表的是正在排队的线程 -- 所以只有head.nx节点拥有抢锁的权利

   // 条件2:tryAcquire(arg)

   // true -- 当前线程获取到了锁

   if (pvNode == head tryAcquire(arg)) {

   // 进入到这里面说明当前线程竞争锁成功了

   // 需要做的操作:

   // 1.设置当前head为当前线程的node

   // 2.协助原来的对象出队

   setHead(node);

   pvNode.nx = null;

   // 因为获取到了锁,所以就return了

   return;

   // 当前不是head.nx节点,或者去尝试获取锁失败了,这个时候都需要去把当前线程park掉

   System.out.println("线程:" + Thread.currentThread().getName() + " 挂起");

   LockSupport.park();

   // 直到某个线程做了当前线程的unPark操作,这个线程才会继续执行

   所以总结一下,lock的逻辑就是:

   1.在没锁的情况下,如果有个线程调用了lock方法,它就会改变lock中的state值。此时state值就不会为0了。

   那么其它线程调用lock方法时,会看到这个state不为0。

   2.然后这个线程会被封装成一个node节点

   3.然后会去尝试竞争一下锁,做一下最后的挽救工作,如果实在挽救不了,就park了

   -- 线程就在这个lock的lock()方法里被阻塞了。就达到了锁的效果

   -- 所有调用这个锁对象lock的方法只能有一个线程能继续执行,然后其它线程会被阻塞,直到这个线程做了unlock操作

   System.out.println("线程:" + Thread.currentThread().getName() + " 唤醒");

   // 什么时候唤醒被park的线程?-- unlock()

   * 把当前线程入队

   * 返回当前线程对应的node节点

   * addWaiter执行完成后,保证当前线程已经入队成功

   private Node addWaiter() {

   Node newNode = new Node(Thread.currentThread());

   // 如何入队?

   // Case1.当前node不是第一个入队的node,队列已经有等待的node了

   // 1.找到newNode的pv节点

   // 2.更新newNode.pvNode = pv节点

   // 3.CAS更新tail为newNode

   // 4.更新pv节点

   Node pvNode = tail;

   if (pvNode != null) {

   newNode.pv = pvNode;

   // 条件成立,说明当前线程成功入队

   if (compareAndSetTail(pvNode, newNode)) {

   pvNode.nx = newNode;

   return newNode;

   // 执行到这里的几种情况

   // 1.tail == null队列是空

   // 2.cas设置当前newNode为tail时失败了 -- 循环入队 -- 自旋

   enq(newNode);

   return newNode;

   * 自旋入队,只有成功之后才返回

   * 1.tail == null 队列是空队列

   * 2.cas设置当前newNode为tail时失败了

   private void enq(Node node) {

   for (; ; ) {

   // 第一种情况:队列是空队列

   // -- 当前线程是第一个抢占锁的线程...

   // 当前持有锁的线程,并没有设置过任何node,所以作为该线程的第一个后驱节点

   // 需要给他擦屁股

   // 给当前持有锁的线程补充一个node作为head节点

   // head节点任何时候都代表当前占用锁的线程

   if (tail == null) {

   // 条件成立:说明当前线程给当前持有锁的线程补充head操作成功了

   if (compareAndSetHead(new Node())) {

   tail = head;

   // 注意,并没有直接返回,而是会继续自旋

   } else {

   // 当前队列中已经有node了,说明这是一个追加node的过程

   // 如何入队呢?

   // 1.找到newNode的pv节点 -- 最新的tail节点

   // 2.更新newNode.pvNode = pv节点

   // 3.CAS更新tail为newNode

   // 4.更新pv节点

   Node pvNode = tail;

   node.pv = pvNode;

   // 条件成立,说明当前线程成功入队

   if (compareAndSetTail(pvNode, node)) {

   pvNode.nx = node;

   return;

   * 尝试获取锁,不会去阻塞线程

   * true -- 抢占成功

   * false -- 抢占失败

   private boolean tryAcquire(int arg) {

   if (state == 0) {

   // 当前state为0

   // 不能直接抢锁 -- 公平锁 -- 先来后到

   // 条件一:!hasQueuedPredecessors() --- 取反之后为true,表示当前线程前面没有等待着的线程

   // 条件二:compareAndSetState(0, arg) - 使用cas的原因:lock方法可能有多线程调用的情况

   // true -- 当前线程抢锁成功

   // (1) volatile -- state被volatile修饰了,所以其它线程能第一时间知道这个值不为0了 -- 缓存能够一致了

   // (2) cas ------- state从0变为arg的操作用cas实现,用于保证只会有一个线程能够改变state的值(0- arg) -- 只会有一个线程能够执行接下来的操作 -- 锁

   // 1.如果cas的变量不用volatile修饰就没有意义:

   // 因为A线程改变了state的值,但是B线程并不知道

   // (可见性,volatile会让B线程中的副本马上失效,然后获取最新的state的值,此时B线程工作空间中的state值就不为0了)

   // 2.如果volatile的变量不用cas去改变它的值的话,也没有意义:

   //· step1.A线程,B线程都拿到了state的副本信息,此时state值为0

   // step2.A线程改变了state的值。B线程还在写,因为state的值改变了,所以B线程工作空间中的state值改变,然后B继续写。

   // 所以所有判断出state值为0的线程都能写成功,并且能执行写成功后续的操作

   // 所以要用cas+volatile去保证只会有一个线程能够写成功这个值

   // Ps.可以看到,如果这些线程想写的值都是同一个值的话,多写了几次,但是结果和只写一次是一致的

   // cas+volatile主要还是去控制写成功之后的操作只会被执行一次,这样就像一个锁一样了

   if (!hasQueuedPredecessors() compareAndSetState(0, arg)) {

   // 抢锁成功

   // 1.将exclusiveOwnerThread设置为当前线程

   this.exclusiveOwnerThread = Thread.currentThread();

   return true;

   // 不会入队任何node,接返回true

   // 接下来第一个竞争失败的线程会先去帮忙创建一个node,然后再执行后续的操作

   // 当前线程前面有线程在等待 多个线程和当前线程一起在尝试获取这个锁,然后当前线程失败了 -- return false;

   } else if (Thread.currentThread() == this.exclusiveOwnerThread) {

   // 执行的时机:

   // 1.当前锁被占用

   // 2.当前线程即为持锁线程

   // 这里面不存在并发。只有当前加锁的线程才有权限修改state

   // 即使是同一个线程多次进入到这,设置state的值,那么它们都是使用的同一个工作空间

   // 不存在不同工作空间下,这个值的不一样的情况(因为没有了缓存)

   // 锁重入的流程

   int c = getState();

   c += arg;

   // TODO 越界判断

   this.state = c;

   return true;

   // 什么时候会返回false?

   // 1.cas加锁失败

   // 2.state大于0,且当前线程不是持锁线程

   return false;

   * 当前线程前面是否有等待着的线程

   * true -- 当前线程前面有等待着的线程

   * false - 当前线程前面没有其它等待着的线程

   * 调用链

   * lock -- acquire - tryAcquire - hasQueuedPredecessors(state值为0时,即当前lock为无主状态)

   * 什么时候返回false?

   * 1.当前队列是空

   * 2.当前线程为head.next节点 -- head.next在任何时候都有权力去争取lock

   private boolean hasQueuedPredecessors() {

   Node h = head;

   Node t = tail;

   Node s;

   // 条件一:h != t

   // true -- 当前队列已经有node了

   // false - h == t

   // case1. h == t == null -- 还没初始化过queue

   // case2. h == t == head

   // 第一个获取锁失败的线程会为当前持有锁的线程补充创建一个head node

   // 条件二:

   // 前置条件:条件一成立

   // 排除几种情况:

   // 条件2.1:极端情况 -- 第一个获取锁失败的线程,会为持锁的线程补充创建head节点,然后在自旋入队

   // step1.cas设置tail成功了

   // step2.head.next = node

   // 在这两步中间的时候,有线程来检查前面是否有等待的线程

   // 这种情况应该返回true:已经有head.next节点了,其它线程来这的时候需要返回true

   // 条件2.2:

   // 前置条件:h.next不是null

   // true -- 条件成立说明当前线程就是持有锁的线程

   // false - 说明当前线程就是h.next节点对应的线程,需要返回false。回头线程就会去竞争锁了

   return h != t ((s = h.nx) == null s.thread != Thread.currentThread());

   private static final Unsafe UNSAFE;

   private static final long STATE_OFFSET;

   private static final long HEAD_OFFSET;

   private static final long TAIL_OFFSET;

   static {

   try {

   Field f = Unsafe.class.getDeclaredField("theUnsafe");

   f.setAccessible(true);

   UNSAFE = (Unsafe) f.get(null);

   STATE_OFFSET = UNSAFE.objectFieldOffset(MiniReentryLock.class.getDeclaredField("state"));

   HEAD_OFFSET = UNSAFE.objectFieldOffset(MiniReentryLock.class.getDeclaredField("head"));

   TAIL_OFFSET = UNSAFE.objectFieldOffset(MiniReentryLock.class.getDeclaredField("tail"));

   } catch (Exception e) {

   throw new Error(e);

   private boolean compareAndSetHead(Node update) {

   return UNSAFE.compareAndSwapObject(this, HEAD_OFFSET, null, update);

   private boolean compareAndSetTail(Node expect, Node update) {

   return UNSAFE.compareAndSwapObject(this, TAIL_OFFSET, expect, update);

   private boolean compareAndSetState(int expect, int update) {

   return UNSAFE.compareAndSwapInt(this, STATE_OFFSET, expect, update);

   * 阻塞的线程被封装成node节点,然后放进FIFO队列

   static final class Node {

   * 封装的线程本身

   Thread thread;

   * 前置节点引用

   Node pv;

   * 后置节点引用

   Node nx;

   public Node(Thread thread) {

   this.thread = thread;

   public Node() {

   public int getState() {

   return state;

   private void setHead(Node node) {

   this.head = node;

   // 当前线程已经是获取到锁的线程

   node.thread = null;

   node.pv = null;

   public void setState(int state) {

   this.state = state;

   public Thread getExclusiveOwnerThread() {

   return exclusiveOwnerThread;

   public void setExclusiveOwnerThread(Thread exclusiveOwnerThread) {

   this.exclusiveOwnerThread = exclusiveOwnerThread;

   public Node getHead() {

   return head;

   public Node getTail() {

   return tail;

   public void setTail(Node tail) {

   this.tail = tail;

  

 

 

  以上就是手写一个模拟的ReentrantLock(手写模块)的详细内容,想要了解更多 手写一个模拟的ReentrantLock的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: