快速了解重入锁实现原理

2022-09-19 11:48:58 浏览数 (1)

什么是重入锁?

从字面意思理解就是“重新进入同步区域”,同一个线程,多次获取一把锁。

哪些锁支持重入呢?

Synchronized

synchronized是支持重入的,它是隐式的获取去重入锁,如下:

代码语言:javascript复制
package com.ams.thread.lesson7;

import lombok.extern.slf4j.Slf4j;

/**
 * 关注微信公众号"AI码师"获取项目源码及2021面试题一套
 *
 * @author: AI码师
 * Date: 2021/12/30 6:08 上午
 * Description:
 */
@Slf4j
public class Example17 {
    public static void main(String[] args) {
        new Thread(new Thread1()).start();
    }

    static class Thread1 implements Runnable {

        @Override
        public void run() {
            synchronized (Example17.class) {
                synchronized (Example17.class) {
                    log.info("成功获取重入锁");
                }
            }
        }
    }
}

ReentrantLock

reentrantLock 也是支持重入的,不过他是需要显示的获取重入锁,并且它还可以支持非公平和公平锁:

代码语言:javascript复制
package com.ams.thread.lesson7;

import lombok.extern.slf4j.Slf4j;

import java.util.Locale;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 关注微信公众号"AI码师"获取项目源码及2021面试题一套
 *
 * @author: AI码师
 * Date: 2021/12/30 6:08 上午
 * Description:
 */
@Slf4j
public class Example18 {
    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        new Thread(new Thread1()).start();
    }

    static class Thread1 implements Runnable {

        @Override
        public void run() {
            lock.lock();
            lock.lock();
            log.info("获取到重入锁");
            lock.unlock();
            lock.unlock();

        }
    }
}

实现重入锁,关键点是什么?

重复进入

当前线程本次获取锁之后,下次在获取改锁的时候,判断为当前线程则直接进入,不阻塞。

锁的释放

如果线程进入了n次,那么它只有释放n次之后,才是真正的释放锁。

我们前面实现了一个锁,但是它是不支持重入的,我们现在给他进行改造:

手写一个重入锁

改造的关键点:

  • 获取锁时,需要判断当前锁是否被占用,如果没有被占用则获取,否则判断是否是当前线程占用,如果是则计数加1,否则等待锁释放。
  • 锁的释放,锁释放时要判断是否是当前线程,如果是,则计数减1,直到为0 ,才是真正释放锁

上代码

代码语言:javascript复制
package com.ams.thread.lesson7;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * 关注微信公众号"AI码师"获取项目源码及2021面试题一套
 * 实现重入锁
 *
 * @author: AI码师
 * Date: 2021/12/30 6:09 上午
 * Description:
 */
public class ReentrantMutexLock implements Lock {
    private final LockAQS lockAQS = new LockAQS();

    @Override
    public void lock() {
        lockAQS.acquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        lockAQS.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return lockAQS.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return lockAQS.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override
    public void unlock() {
        lockAQS.release(1);
    }

    @Override
    public Condition newCondition() {
        return lockAQS.newCondition();
    }

    private static class LockAQS extends AbstractQueuedSynchronizer {
        /**
         * 重新获取锁的逻辑,只有当前同步状态为0,才允许获取成功
         *
         * @param arg
         * @return
         */
        @Override
        protected boolean tryAcquire(int arg) {
            // 通过cas机制去设置新值,如果当前锁没有被占用,则期望值肯定为0 并且新值为 获取的资源数量
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
                // 当前则为重新获取同一把锁,则进行计数加1
            } else if (getExclusiveOwnerThread() == Thread.currentThread()) {
                return compareAndSetState(getState(), getState()   1);
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            // 首先判断当前state 如果当前状态值不是大于0 则释放非法
            if (getState() == 0) {
                return false;
            }
            //  判断当前获取锁的线程是否是当前线程 如果是的,则可以释放锁,否则抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            // 计数减去当前获取的资源
            setState(getState() - 1);
            // 计数为0,则代表全部释放,将独占线程标识置空
            if (getState() == 0) {
                setExclusiveOwnerThread(null);
            }
            return true;
        }

        Condition newCondition() {
            return new ConditionObject();
        }
    }

}

代码语言:javascript复制
package com.ams.thread.lesson7;

import cn.hutool.core.thread.ThreadUtil;
import com.ams.thread.lesson5.MyMutexLock;
import lombok.extern.slf4j.Slf4j;

/**
 * 关注微信公众号"AI码师"获取项目源码及2021面试题一套
 * 验证重入锁
 * @author: AI码师
 * Date: 2021/12/30 6:08 上午
 * Description:
 */
@Slf4j
public class Example19 {
private static ReentrantMutexLock reentrantMutexLock = new ReentrantMutexLock();

    public static void main(String[] args) {
        new Thread(new Thread1()).start();
        new Thread(new Thread2()).start();
    }
    static class Thread1 implements Runnable {

        @Override
        public void run() {
            reentrantMutexLock.lock();
            ThreadUtil.sleep(1000);
            log.info("Thread1 获取到锁");
            ThreadUtil.sleep(1000);
            reentrantMutexLock.lock();
            ThreadUtil.sleep(1000);
            log.info("Thread1 再次获取到锁");
            ThreadUtil.sleep(1000);
            reentrantMutexLock.unlock();
            ThreadUtil.sleep(1000);
            log.info("Thread1 释放锁");
            reentrantMutexLock.unlock();
            ThreadUtil.sleep(1000);
            log.info("Thread1 释放所有锁");
        }
    }
    static class Thread2 implements Runnable {

        @Override
        public void run() {
            reentrantMutexLock.lock();
            ThreadUtil.sleep(1000);
            log.info("Thread2 获取到锁");
            reentrantMutexLock.lock();
            ThreadUtil.sleep(1000);
            log.info("Thread2 再次获取到锁");
            ThreadUtil.sleep(5000);
            ThreadUtil.sleep(1000);
            reentrantMutexLock.unlock();
            log.info("Thread2 释放锁");
            reentrantMutexLock.unlock();
            ThreadUtil.sleep(1000);
            log.info("Thread2 释放所有锁");
        }
    }
}

通过执行结果可以看出:输出结果是按照我们期望执行的。需要注意的是,使用该锁在调用unlock方法时,锁被立即释放,并不会等待当前方法块执行完,和synchronized同步块,所以严禁在unlock方法后继续写需要进行同步逻辑

关注公众号领取2022最新面试题一套和项目源码

0 人点赞