人海源码系列-Reentrantlock

今天讲reentrantlock源码,讲解的思路是先自己手写最简单lock,然后一步一步增加功能,然后再去看源码

目录

手写版本1,最基础的lock,抢不到锁就自旋

手写版本2,抢不到锁就把自己放入队列并且阻塞自己


手写版本1,最基础的lock,抢不到锁就自旋

先写基础部分,获取unsafe类和stateOffset,这个主要是也用于CAS操作

    private final static Unsafe unsafe = UnsafeUtils.getUnsafe();//1. 获取unsafe类

    private final static long stateOffset; //2. 定义偏移量

    static {
        try {
            stateOffset = unsafe.objectFieldOffset
                    (MyLock2.class.getDeclaredField("state"));
        } catch (NoSuchFieldException e) {
            throw new Error(e);
        }
    }

接着写lock方法和unlock方法,并且定义一个int类型的变量state当做锁

    private int state = 0;    

    public void lock() {
        //抢锁
        if (!unsafe.compareAndSwapInt(this, stateOffset, 0, 1)) {
            //抢锁失败自旋,这里睡眠100ms是为了让出cpu,不然一直抢占着cpu是不行的
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void unlock() {
        //放锁
        state = 0;
    }

这样,一个最最简单的lock就完成了,让我们测试一下

    public static void main(String[] args) throws InterruptedException {
        RenhaiLock lock = new RenhaiLock();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
//                lock.lock();
                k++;
//                lock.unlock();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
//                lock.lock();
                k++;
//                lock.unlock();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(k);
    }

输出结果

Connected to the target VM, address: '127.0.0.1:65421', transport: 'socket'
112277
Disconnected from the target VM, address: '127.0.0.1:65421', transport: 'socket'

Process finished with exit code 0

然后再看加上lock的测试结果

    public static void main(String[] args) throws InterruptedException {
        RenhaiLock lock = new RenhaiLock();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                lock.lock();
                k++;
                lock.unlock();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                lock.lock();
                k++;
                lock.unlock();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(k);
    }

测试结果

Connected to the target VM, address: '127.0.0.1:59131', transport: 'socket'
200000
Disconnected from the target VM, address: '127.0.0.1:59131', transport: 'socket'

Process finished with exit code 0

测试结果证明我们的lock生效了,但是这个版本的lock存在一个问题,就是当抢不到锁的时候会进入自旋,如果抢到锁的任务耗时太长,那么就会浪费很多cpu资源,所以我们要增加一个队列用于存放抢不到锁的线程,并且用LockSupport.park()方法阻塞它,然后解锁的时候去唤醒队列中的线程,这样在获取锁线程执行时间再长也不会浪费资源,因为抢不到锁的线程是在队列中阻塞着的.

手写版本2,抢不到锁就把自己放入队列并且阻塞自己

lock方法的改动

unlock方法的改动

这么一改,当任务执行时间过长也不怕,等待队列中的线程是被阻塞的,不会浪费cpu资源

版本三,增加可重入功能

整体思路:增加一个变量存放持有锁的线程,当持有锁的线程获取锁时直接把state+1,持有锁的线程要释放锁的时候就把state-1

    private int state = 0;

    private LinkedBlockingDeque<Thread> waiters = new LinkedBlockingDeque<>();

    //持有锁的线程
    private Thread holdThread;

    public void lock() {
        if (!tryLock()) { // 先抢锁,抢锁成功直接退出,抢锁失败加入则等待队列
            waiters.add(Thread.currentThread()); // 抢锁失败,加入等待队列中
            while (true) { // 自旋 进入:抢锁 -> 抢锁失败 -> 阻塞自己 -> 抢锁 的循环
                if (tryLock()) { // 抢锁
                    waiters.poll(); // 抢锁成功,把自己从等待队列中移除
                    return; //  退出循环
                }
                LockSupport.park(); // 抢锁失败,阻塞自己,等待被唤醒
            }
        }
    }

    public boolean tryLock() {
        Thread current = Thread.currentThread();
        if (holdThread != null && holdThread == current) {
            state++; // 如果获取锁的线程尝试再次获取锁,则state+1
            return true;
        }
        if (unsafe.compareAndSwapInt(this, stateOffset, 0, 1)) {
            holdThread = current; // 获取到锁就把holdThread=当前线程
            return true;
        }
        return false;
    }

    public void unlock() {
        state--;// 释放锁,直接把state-1
        if (state == 0) {
            //唤醒队列中第一个线程
            Thread peek = waiters.peek();
            LockSupport.unpark(peek);
        }
    }

测试代码

    public static void main(String[] args) throws InterruptedException {
        RenhaiLockV2Reentrant lock = new RenhaiLockV2Reentrant();
        Thread t1 = new Thread(() -> {
            lock.lock();
            logger.info("抢到锁");
            lock.lock();
            logger.info("抢到锁");
            lock.unlock();
            logger.info("解锁");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.unlock();
            logger.info("解锁");
        });
        Thread t2 = new Thread(() -> {
            lock.lock();
            logger.info("抢到锁");
            lock.unlock();
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
    }

测试结果,线程1是等线程0释放两次锁才抢到锁的,释放第二次锁之前线程阻塞1秒钟特意给线程1留出时间,然而那时候线程0还没释放完毕,还持有一把锁,所以即使阻塞一秒钟也没办法抢到锁

Connected to the target VM, address: '127.0.0.1:51910', transport: 'socket'
12:37:48.362 [Thread-0] INFO com.lz.demo.concurrent.renhai.RenhaiLockV2Reentrant - 抢到锁
12:37:48.366 [Thread-0] INFO com.lz.demo.concurrent.renhai.RenhaiLockV2Reentrant - 抢到锁
12:37:48.366 [Thread-0] INFO com.lz.demo.concurrent.renhai.RenhaiLockV2Reentrant - 解锁
12:37:49.369 [Thread-0] INFO com.lz.demo.concurrent.renhai.RenhaiLockV2Reentrant - 解锁
12:37:49.369 [Thread-1] INFO com.lz.demo.concurrent.renhai.RenhaiLockV2Reentrant - 抢到锁
0
Disconnected from the target VM, address: '127.0.0.1:51910', transport: 'socket'

Process finished with exit code 0

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值