ReentrantLock底层原理、手写Lock锁

 

    ReentrantLock锁是一个轻量级锁,底层其实就是用自旋锁实现的,lock锁不依赖操作系统,而是使用java实现的锁,当我们调用lock方法的时候,在内部其实调用了Sync.lock()方法,而Sync继承了AbstractQueuedSynchronizer,简称AQS,所以在底层调用的其实是AQS的 lock() 方法;

 

 

ReentrantLock和synchronized不同的是,synchronized在jdk1.5以前是一把很重的锁,每次使用时都需要向操作系统申请,所以会耗费很大的资源,且效率不高,但ReentrantLock 的底层是由cas实现的,cas本身是自旋锁,也叫无锁,因为轻量级锁不需要像操作系统申请锁资源,所以不会进入阻塞状态,所以lock锁的效率要比synchronized高很多;

我们先看看lock锁的底层原理流程图

 

好了,知道了这些流程,那接下来我们自己实现一个简单的Lock锁吧!

自定义的锁   XdLock.java

package com.test;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;

public
// 手写 的 lock 锁
class XdLock implements Lock {

    // 缓存任务的队列
    LinkedBlockingQueue<Thread> queue = new LinkedBlockingQueue<>();

    // 原子类 ,登记我们的线程
    AtomicReference<Thread> cas = new AtomicReference<>();


    // 上锁
    @Override
    public void lock() {
        //  使用cas进行尝试上锁, 如果为空,将AtomicReference的值设为当前线程
        while (!cas.compareAndSet(null, Thread.currentThread())) {
            // 抢锁锁失败了!
            queue.add(Thread.currentThread()); // 将线程进入队列,等候执行
            System.out.println(Thread.currentThread().getName() + ":进入阻塞");
            // 让当前线程阻塞,
            LockSupport.park(); // 注意:调用park() 方法后会进入阻塞状态,下面的代码就不会在执行了,除非别的线程调用了 unpark(Thread) 方法来唤醒当前线程
            System.out.println(Thread.currentThread().getName() + ":已放开阻塞线程");
            queue.remove(Thread.currentThread()); // 移除线程
        }

    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    // 解锁
    @Override
    public void unlock() {

        // 只有持有锁的线程才能解锁,
        // cas.compareAndSet(Thread.currentThread(),null) 的意思是:如果 AtomicReference 记录的是当前线程,表示当前线程持有锁,解锁的操作就是设为null;
        if (cas.compareAndSet(Thread.currentThread(), null)) {

            // 唤醒其他等待的线程
            for (Object object : queue.toArray()) {
                Thread thread = (Thread) object;
                System.out.println(Thread.currentThread().getName() + ":进行唤醒操作");
                LockSupport.unpark(thread); // 唤醒其他阻塞的线程;
                System.out.println(Thread.currentThread().getName() + ":唤醒成功!!");

            }
        } else {
            // 解锁失败,不做任何操作
        }

    }

    @Override
    public Condition newCondition() {
        return null;
    }

}

运行测试  LockTest.java

package com.test;

public class LockTest {


    private XdLock lock = null;

    public static void main(String[] args) {

        // 实例化锁
        XdLock lock = new XdLock();

        // 创建2个线程来争抢锁
        LockTest lockTest = new LockTest(lock);
        Thread thread = new Thread(lockTest::show);
        thread.setName("线程AA");

        Thread thread1 = new Thread(lockTest::show);
        thread1.setName("线程BB");

        thread.start();
        thread1.start();
    }

    // 构造方法
    public LockTest(XdLock lock) {
        this.lock = lock;
    }

    public void show() {

        lock.lock();  // 上锁
        int i = 100;
        while (i > 0) {
            System.out.println(Thread.currentThread().getName() + ":叶新东 " + i);
            i--;
        }
        lock.unlock();  // 解锁
    }

}

 

首次上锁和二次上锁

ReentrantLock 的首次上锁和二次上锁流程又不一样,当给第一个线程上锁的时候,是不进入队列的,给第二个线程上锁的时候才会进入队列,超过2次以后每次上锁都会进入队列,上锁流程如下:

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

java叶新东老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值