手写AQS核心代码

1:AQS简介

  • AQS(AbstractQueuedSynchronizer):j.u.c下的Lock就是使用AQS实现的;为了使得多线程在并发访问资源的时候的安全性,纯Java语言实现(其中synchronized底层是由c++实现的)
  • AQS支持线程抢占两种锁——独占锁和共享锁:
  • 独占锁:同一个时刻只能被一个线程占有,如ReentrantLock,ReentrantWriteLock等,它又可分为:
  1. 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
  2. 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
  • 共享锁:同一时间点可以被多个线程同时占有,如ReentrantReadLock,Semaphore等

2:unsafe魔术类

  • 直接绕过虚拟机,操作底层的内存。方法大都是native方法(底层是c++)
  • Java不建议使用此类,如果需要使用,则必须使用反射机制进行实例化;
  • Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,同时也带来了指针的问题。
  • 使用 ReentrantLock 进行多线程开发,当一个线程抢占锁失败时,线程将被挂起,实现线程挂起的正是Unsafe类。

3:手写公平独占AQS

  • 尝试获取锁
//尝试进行加锁的算法
    public boolean acquire(){
        //CAS比较与交换算法,保证任意时刻只有一个线程可以拿到
        //当前线程
        Thread current = Thread.currentThread();
        //获取到当前state初始值
        int c = getState();
        if( c == 0){ //目前锁还没有被持有
            //如果等待队列中没有进程(实现公平锁)或者当前线程是等待队列中第一个线程,并且此线程修改成功了(加锁成功),则设置持有锁的线程为本线程;
            if((waiters.size()==0||current == waiters.peek())&&compareAndSwapState(0,1)){
                setLocalHolder(current);
                return true;
            }
        }
        return false;
    }
  • 加锁:
  1. 任意时刻,只能有一个线程加锁成功(原子性)
  2. 使用CAS机制(CAS,一种乐观锁机制,如果对象当前值和期望值一样,那么则将对象的值设置成新值。和悲观锁不一样,它不需要抢占锁,它是一种尝试性的,能有效地提高效率,它的全称是 compareAndSwap ,依赖于硬件的原子操作实现。)
public void lock(){
        //如果加锁成功
        if(acquire()){
            return;
        }
        //当前线程
        Thread current = Thread.currentThread();
        //没有获取成功,将线程放入等待队列中
        waiters.add(current);

        //如果没有加锁成功,则使此线程一直自旋在本方法
        for (;;){
            //让步出线程
            //1:Thread.yield();但是循环之后还是在占用cpu,不推荐
            //2:Thread.sleep(1000);不推荐,原因如下
            //(1):设置时常大之后,其他线程已经释放锁,本线程还在睡眠,浪费时间
            //(2):设置时常小之后,导致不停的睡眠启动线程,系统开销大
            //3:Thread.wait();不推荐,因为在唤醒线程的时候,无法准确指定唤醒那一个线程;
            //4:使用Unsafe类中的park()和unpark()方法,进行手动的释放和开启线程(此两种方法已经重写在了jdk的LockSupport类中)
            /*
                //jdk中的方法体
                public static void park(Object blocker) {
                    Thread t = Thread.currentThread();
                    setBlocker(t, blocker);
                    U.park(false, 0L);
                    setBlocker(t, (Object)null);
                }
             */

            //判断当前线程是否是第一个等待的线程(保证公平),如果是则继续循环获取锁,获取成功跳出循环
            if((current==waiters.peek()) && acquire()){
                //第一个线程获取到锁之后,将它从等待队列中移除
                waiters.poll();
                return;
            }
            //阻塞当前线程(将此线程的所有数据放入内存中的运行时数据区)
            LockSupport.park(current);
        }
  • 解锁
//解锁方法
    public void unLock(){
        //判断当前对象是不是之前拿到锁的对象
        if(Thread.currentThread()!=localHolder){
            throw new RuntimeException("LocalHolder is not current thread");
        }
        //将state和LocalHolder都置为空,表示当前锁空闲
        int state = getState();
        if(compareAndSwapState(state,0)){
            setLocalHolder(null);
            //当前锁空闲后,如果等待队列中有线程,则唤醒此线程
            Thread first = waiters.peek();
            if(first!=null){
                LockSupport.unpark(first);
            }
        }
    }
  • 使用队列waiters来存放当前等待获取锁的线程
//定义一个线程安全(底层是使用CAS算法保证线程安全的)的队列,用于保存此时没有获取到锁的线程
private ConcurrentLinkedQueue<Thread> waiters = new ConcurrentLinkedQueue<>();
  • 通过反射机制获得Unsafe对象
//通过反射机制获取到Unsafe对象
private static final Unsafe unsafe = UnsafeInstance.reflectGetUnsafe();

 

package util;

import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class UnsafeInstance {

    //获取Unsafe对象
    public static Unsafe reflectGetUnsafe() {

        //通过反射机制获取到Unsafe类
        Field field = null;
        try {
            field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            return (Unsafe) field.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
  • 全部代码
package lock;

import sun.misc.Unsafe;
import util.UnsafeInstance;

import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.locks.LockSupport;

public class AQS {

    //用来记录当前加锁状态,记录加锁次数,
    //值为0/1,为1时表示已经有一个线程持有了锁
    private volatile int state = 0;

    //表示当前只有锁的对象
    private Thread localHolder;

    //定义一个线程安全(底层是使用CAS算法保证线程安全的)的队列,用于保存此时没有获取到锁的线程
    private ConcurrentLinkedQueue<Thread> waiters = new ConcurrentLinkedQueue<>();

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
    }

    public Thread getLocalHolder() {
        return localHolder;
    }

    public void setLocalHolder(Thread localHolder) {
        this.localHolder = localHolder;
    }

    //尝试进行加锁的算法
    public boolean acquire(){
        //CAS比较与交换算法,保证任意时刻只有一个线程可以拿到
        //当前线程
        Thread current = Thread.currentThread();
        //获取到当前state初始值
        int c = getState();
        if( c == 0){ //目前锁还没有被持有
            //如果等待队列中没有进程(实现公平锁)或者当前线程是等待队列中第一个线程,并且此线程修改成功了(加锁成功),则设置持有锁的线程为本线程;
            if((waiters.size()==0||current == waiters.peek())&&compareAndSwapState(0,1)){
                setLocalHolder(current);
                return true;
            }
        }
        return false;
    }

    //加锁
    public void lock(){
        //如果加锁成功
        if(acquire()){
            return;
        }
        //当前线程
        Thread current = Thread.currentThread();
        //没有获取成功,将线程放入等待队列中
        waiters.add(current);

        //如果没有加锁成功,则使此线程一直自旋在本方法
        for (;;){
            //让步出线程
            //1:Thread.yield();但是循环之后还是在占用cpu,不推荐
            //2:Thread.sleep(1000);不推荐,原因如下
            //(1):设置时常大之后,其他线程已经释放锁,本线程还在睡眠,浪费时间
            //(2):设置时常小之后,导致不停的睡眠启动线程,系统开销大
            //3:Thread.wait();不推荐,因为在唤醒线程的时候,无法准确指定唤醒那一个线程;
            //4:使用Unsafe类中的park()和unpark()方法,进行手动的释放和开启线程(此两种方法已经重写在了jdk的LockSupport类中)
            /*
                //jdk中的方法体
                public static void park(Object blocker) {
                    Thread t = Thread.currentThread();
                    setBlocker(t, blocker);
                    U.park(false, 0L);
                    setBlocker(t, (Object)null);
                }
             */

            //判断当前线程是否是第一个等待的线程(保证公平),如果是则继续循环获取锁,获取成功跳出循环
            if((current==waiters.peek()) && acquire()){
                //第一个线程获取到锁之后,将它从等待队列中移除
                waiters.poll();
                return;
            }
            //阻塞当前线程(将此线程的所有数据放入内存中的运行时数据区)
            LockSupport.park(current);
        }

    }

    //解锁方法
    public void unLock(){
        //判断当前对象是不是之前拿到锁的对象
        if(Thread.currentThread()!=localHolder){
            throw new RuntimeException("LocalHolder is not current thread");
        }
        //将state和LocalHolder都置为空,表示当前锁空闲
        int state = getState();
        if(compareAndSwapState(state,0)){
            setLocalHolder(null);
            //当前锁空闲后,如果等待队列中有线程,则唤醒此线程
            Thread first = waiters.peek();
            if(first!=null){
                LockSupport.unpark(first);
            }
        }
    }


    /*
     * 原子操作。
     * @param except:目前值
     * @param update:要更新后的值
     */
    public final boolean compareAndSwapState(int except,int update){
        return unsafe.compareAndSwapInt(this,stateOffset,except,update);
    }

    //通过反射机制获取到Unsafe对象
    private static final Unsafe unsafe = UnsafeInstance.reflectGetUnsafe();

    //在内存的偏移量值,因为CAS种需要此参数
    private static long stateOffset;

    static {
        try {
            //找到state对象在内存中的偏移量
            stateOffset = unsafe.objectFieldOffset(AQS.class.getDeclaredField("state"));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

}

4:测试锁

  • 测试代码:(新建10个线程,执行m方法,自增一个共享变量10000次,期望输出100000)
  • 但由于没有保证原子性,值永远小于100000;
package main;

import lock.AQS;

import java.util.ArrayList;
import java.util.List;

public class AQSTest {
        
    //共享变量
    private static int count = 0;
    
    //创建手写的锁
    private static AQS syn = new AQS();
    
    //线程执行的方法
    public void m() {
        //syn.lock();
        for (int i = 0; i < 10000; i++) {
            count++;
        }
        //syn.unLock();
    }

    public static void main(String[] args) {

        AQSTest t=new AQSTest();
        List<Thread> threads=new ArrayList<>();
    
        //创建10个线程,并执行m方法
        for (int i = 0; i < 10; i++) {
            threads.add(new Thread(t::m,"thread-"+i));
        }

        threads.forEach((o)->o.start());

        threads.forEach((o)->{
            try {
                o.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        //输出值
        System.out.println(count);
    }

}
  • 测试结果:

5:加上手写锁之后

  • 测试结果:

视频地址:同步器核心原理刨析

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值