自定义一把lock锁

一、概述

学习完lock锁之后,想要来写一把自己的lock锁。

但是我感觉比较初级,应该在学习Lock锁之前来写的。反正也想着总结一下。

二、lock锁的前提

1、锁的标识,当前锁是用state来进行标识的;

2、加锁、解锁方法;

3、如果锁是自由状态,加锁成功的话需要让其返回加锁成功状态;

4、如果锁不是自由状态,则让其加锁失败。所谓的加锁失败,是让其线程陷入到阻塞中或者是自旋;

5、解锁方法原则:释放锁的同时还需要唤醒其他线程(当前锁中使用的是自旋,所以不需要唤醒)

对于第三点要来做个详细说明:

存在一个现象,对于外部线程来说,不知道锁是否是自由状态。 那么就意味着,外部线程来访问锁的时候,就分为了两种情况:被其他线程持有锁 或者 是当前锁是自由状态, 那么对于多线程来说:t1,t2,t3,t4,如果当前锁被t1持有,t2,t3,t4线程因为持有不到锁,而去自旋,但是此时,如果t5线程进入,而t1线程又刚刚好将锁释放掉,而锁又被刚刚加入被t5持有了,那么对于t2,t3,t4线程来说,是不公平的,因为已经自旋了这么久了。因为外部的线程到来而导致当前线程无法使用。所以需要考虑到公平和非公平的条件。

如果是公平锁,那么线程也应该等着前面的t2,t3,t4线程执行完之后,然后轮到t5;如果是不公平的,那么就无所谓
但是如果是多线程条件下,自旋是很消耗CPU资源的。所以为了避免CPU资源的消耗,应该考虑让线程阻塞。那么阻塞应该要被唤醒, 那么唤醒,那么就应该考虑到应该使用容器来盛放多线程,在锁释放之后,将线程再唤醒

三、代码实现

public class LigLock {

    private final static Logger logger = LoggerFactory.getLogger(LigLock.class);

    //标识---加锁成功=1  如果自由状态=0
    volatile int state=0;

    //unsafe 对象,因为cas操作需要这个对象
    static Unsafe unsafe;

    //state这个字段在ShadowLock对象当中的内存偏移量
    private static long stateOffset;

    /**
     * 1、获取unsafe对象,这个对象的获取稍微有点复杂,目前不需要关心
     * 2、通过unsafe对象得到state属性在当前对象当的偏移地址
     */
    static {
        try {
            unsafe = getUnSafe();
            stateOffset = unsafe.objectFieldOffset(LigLock.class.getDeclaredField("state"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //加锁方法
    @SneakyThrows
    public void lock(){
        //compareAndSet(0,1)  当某个属性(state)他的值如果为0 则修改1

        //效率高 cas 无锁 不会发生内核态切换

        //t1 执行 compareAndSet(0,1) before state =0;after  true ---->state=1
        //t1 执行 compareAndSet(0,1) before state =1

        // 这里保证了原子性操作
        while(!compareAndSet(0,1)){
            //加锁失败
            logger.info("当前线程{}在等待获取得到锁",Thread.currentThread().getName());
            Thread.sleep(10L);
        }
        // 如果成功,则进行赋值
        Thread currentThread = Thread.currentThread();
        logger.info("当前线程获取得到了锁,当前线程的名称是:{}",currentThread.getName());
    }

    public void unlock(){
        //思考这里为什么不要cas? 因为这里不需要来进行只有已经持有锁了的线程才可以操作
        state=0;
        logger.info("当前线程释放了锁,当前线程的名称是:{}",Thread.currentThread().getName());
    }


    //获取unsafe对象
    private static Unsafe getUnSafe() throws NoSuchFieldException, IllegalAccessException {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        return (Unsafe) theUnsafe.get(null);
    }

    //cas 修改state的值
    //expect预期值,如果相同则把state的值改成x 原子操作
    private boolean compareAndSet(int expect,int x){
        //stateOffset 一个地址 【state属性的地址】
        return unsafe.compareAndSwapInt(this, stateOffset, expect, x);
    };


}

测试代码:

public class LigLockTest {

    static LigLock lock = new LigLock();
    private final static Logger logger = LoggerFactory.getLogger(LigLockTest.class);
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            lock.lock();
            logger.info("当前是线程{}在执行",Thread.currentThread().getName());
            try {
                TimeUnit.MICROSECONDS.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.unlock();
            logger.info("-------------------------");
        }, "t1");

        Thread t2 = new Thread(() -> {
            lock.lock();
            logger.info("当前是线程{}在执行",Thread.currentThread().getName());
            lock.unlock();
            logger.info("-------------------------");
        }, "t2");

        t1.start();
        TimeUnit.MICROSECONDS.sleep(10);
        t2.start();
    }

}

四、缺点总结

1、不能够支持重入;

2、不支持公平锁;

3、性能较低。因为自旋比较消耗CPU资源;

4、没有对排队线程记录以及当前线程记录;

posted @   雩娄的木子  阅读(117)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示