手写实现AQS队列同步器

#前言


一直在用ReentrantLock跟synchronized加锁,但是没认真去研究过里面具体实现的原理。抽时间看了书籍跟视频,把自己学习的心得记录下来。


#正文


首先我们创建一个maven项目,然后新建一个Mylock.java文件,定义一些必要的变量:

     /**
     * 加锁状态
     */
    private volatile int state=0;

    /**
     * 锁的持有者
     */
    private Thread lockHolder =null;

    /**
     * 获取锁等待队列
     */
    private ConcurrentLinkedQueue<Thread> waiters = new ConcurrentLinkedQueue<>();

    /**
     * unsafe魔法类
     */
    private static final Unsafe UNSAFE = UnSafeTool.getUnSafe();

    /**
     * 锁状态偏移量
     */
    private static long stateOffset;

Unsafe这个类是一个特别的类,提供了丰富的原子操作方法,有兴趣的请自行去查询相关资料了解,这里不再作过多介绍.
它是不能直接实例化的,所以需要反射出来,下面是Unsafe实例获取方法

 public static Unsafe getUnSafe(){
        try{
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            return (Unsafe) f.get(null);
        }
        catch (Exception e){
            return null;
        }
    }

锁状态偏移量其实就是锁状态state的内存地址.
既然是同步器,肯定是先获取锁.下面是加锁方法定义

  /**
     * 加锁
     */
    public void lock(){
        if (acquire()){
            System.out.println(Thread.currentThread().getName() + "加锁成功");
            return;
        }
        //加锁失败,则进入等待队列,等待被唤醒,
        waiters.offer(Thread.currentThread());

        //自旋去获取锁
        for (;;){
            if (acquire()){
                //加锁成功,出队列
                System.out.println(Thread.currentThread().getName() + " 加锁成功");
                waiters.remove(Thread.currentThread());
                return;
            }
            //阻塞并释放cpu使用权
            LockSupport.park();
        }

    }

没有获取到锁的线程会进行自旋,并使用 LockSupport.park()方法释放cpu使用权.
下面我们再看acquire尝试加锁方法:

  /**
     * 尝试加锁
     * @return
     */
    private boolean acquire(){

        //先判断锁是否被持有
        if(getState() == 0)
        {
            if(!shouldPark() &&compareAndSwapInt(getState(),1))
            {
                setLockHolder(Thread.currentThread());
                return true;
            }
        }
        //支持可重入
        if (Thread.currentThread().equals(getLockHolder())){
            int update=getState();
            setState(update++);
            return true;
        }
        return false;
    }

这里使用Unsafe的CAS更新锁状态来达到原子性修改锁状态,state锁状态我使用了volatile保证了线程之间的可见性.

  /**
     * CAS更新锁状态
     * @param expect 预期值
     * @param update 更新后的值
     * @return
     */
    private boolean compareAndSwapInt(int expect, int update){
        return UNSAFE.compareAndSwapInt(this, stateOffset, expect, update);
    }

这里就是加锁的所有过程
下面是释放锁的方法定义

 /**
     * 释放锁
     */
    public void unLock()
    {
         //检验是否是持锁线程
        if (Thread.currentThread() != getLockHolder()){
            throw new RuntimeException(MessageFormat.format("LockHolder is not current thread, currentThead is {0}," +
                    "LockHolder thread is {1}!",Thread.currentThread().getName(),getLockHolder().getName()));
        }
        //清空持锁线程
        setLockHolder(null);
        //恢复锁状态,必须先清空持锁线程再回复锁状态,否则可能发生:新线程设置持锁线程后,在这儿这里清空了
        setState(0);
        System.out.println(Thread.currentThread().getName() + " 释放锁");
        //唤醒等待队列中第一个线程
        Thread first = waiters.peek();
        if (first != null){
            LockSupport.unpark(first);
            System.out.println(Thread.currentThread().getName() + " 唤醒 " + first.getName());
        }
    }

释放锁比较简单,就是持有锁线程把锁状态设置回0(最原始的状态),清空持有锁,再从唤醒等待队列获取第一个线程对象,并使用LockSupport.unpark唤醒,让它去尝试加锁
下面是这个同步器的用法,跟java 给我们并发工具类ReentrantLock用法差不多,ReentrantLock公平锁跟我们写的这个锁原理差不多。

 mylock.lock();
 //do something
 mylock.unLock();

源码

猜你喜欢

转载自blog.csdn.net/lin06051180/article/details/108006444