0.AQS抽象队列同步器

AQS的简单定义

AQS简单来说就是提供了对资源的占用、释放、线程的等待、唤醒等等接口和具体实现。

AQS的推演

ReentranLock类给我们提供了lock、unlock的机制,对多线程进行一个加锁解锁的机制。实现了线程安全,这套机制如何自己实现呢。

  • 自定义锁
/**
 * 自定义(独享锁)
 */
public class CustomLock  implements Lock {

    //如何判断一个锁的状态或者拥有者
    volatile AtomicReference<Thread> owner = new AtomicReference<>();
    //保存正在等待的线程
    volatile LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();

    @Override
    public void lock() {
        //当前线程值加一次队列中,防止重复加入
        boolean addQue = true;
        //如果没有拿到锁
        while(!tryLock()){
            if(addQue) {
                //加入到等待集合,如果不加入到等待集合。当前线程唤醒后,无法知晓其他线程如何唤醒
                waiters.offer(Thread.currentThread());
                addQue = false;
            }else{
                //阻塞挂起当前线程,不要在往下跑~
                LockSupport.park();
            }
        }
        //如果当前线程拿到锁,将当前线程从队列中删除;
        waiters.remove(Thread.currentThread());
    }

    @Override
    public boolean tryLock() {
        //如果当前的锁没有被占用,则将当前线程设置为锁并且返回true
        return  owner.compareAndSet(null,Thread.currentThread());
    }

    @Override
    public void unlock() {
        //释放拥有者
        if(owner.compareAndSet(Thread.currentThread(),null)){
            //通知其他的等待者
            Iterator<Thread> iterator = waiters.iterator();
            while (iterator.hasNext()){
                Thread next = iterator.next();
                LockSupport.unpark(next); //唤醒
            }
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }


    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }


    @Override
    public Condition newCondition() {
        return null;
    }
}
  • 与Reentranlock类的lock比较
public void lock() {
    sync.lock();
}
abstract static class Sync extends AbstractQueuedSynchronizer {}
  • 总结

发现在Reentranlock中使用sync调用lock,相较于自定义实现的代码简洁了许多。翻阅到sync的类定义可知,其实现了aqs的抽象类。

封装自定义的锁=》结论

  • 将自定义的类分装
/**
 * 组定义AQS类、
 * 抽象队列同步器
 * state, owner, waiters
 */
public class CustomAqs {
    // acquire、 acquireShared : 定义了资源争用的逻辑,如果没拿到,则等待。
    // tryAcquire、 tryAcquireShared : 实际执行占用资源的操作,如何判定一个由使用者具体去实现。
    // release、 releaseShared : 定义释放资源的逻辑,释放之后,通知后续节点进行争抢。
    // tryRelease、 tryReleaseShared: 实际执行资源释放的操作,具体的AQS使用者去实现。

    // 1、 如何判断一个资源的拥有者
    public volatile AtomicReference<Thread> owner = new AtomicReference<>();
    // 保存 正在等待的线程
    public volatile LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();

    // 独占资源相关的代码
    public boolean tryAcquire() { // 交给使用者去实现。 模板方法设计模式
        throw new UnsupportedOperationException();
    }

    public void acquire() {
        boolean addQ = true;
        while (!tryAcquire()) {
            if (addQ) {
                // 没拿到锁,加入到等待集合
                waiters.offer(Thread.currentThread());
                addQ = false;
            } else {
                // 阻塞 挂起当前的线程,不要继续往下跑了
                LockSupport.park(); // 伪唤醒,就是非unpark唤醒的
            }
        }
        waiters.remove(Thread.currentThread()); // 把线程移除
    }

    public boolean tryRelease() {
        throw new UnsupportedOperationException();
    }

    public void release() { // 定义了 释放资源之后要做的操作
        if (tryRelease()) {
            // 通知等待者
            Iterator<Thread> iterator = waiters.iterator();
            while (iterator.hasNext()) {
                Thread next = iterator.next();
                LockSupport.unpark(next); // 唤醒
            }
        }
    }
}
/**
 * 自定义(独享锁)
 */
public class CustomLockDemo2 implements Lock {

    CustomAqs customAqs = new CustomAqs(){

        @Override
        public boolean tryAcquire() {
            return  owner.compareAndSet(null,Thread.currentThread());
        }

        @Override
        public boolean tryRelease() {
            return  owner.compareAndSet(Thread.currentThread(),null);
        }
    };

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

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

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public Condition newCondition() {
        return null;
    }
}
  • 总结

回顾aqs的定义对资源占用,释放,线程的等待,唤醒等接口的具体实现,以上方法通过模板涉及模式实现了AQS的封装

Semaphore

使用到aqs的共享资源

  • 自定义源码示例
// 抽象队列同步器
// state, owner, waiters
public class NeteaseAqs {
    // acquire、 acquireShared : 定义了资源争用的逻辑,如果没拿到,则等待。
    // tryAcquire、 tryAcquireShared : 实际执行占用资源的操作,如何判定一个由使用者具体去实现。
    // release、 releaseShared : 定义释放资源的逻辑,释放之后,通知后续节点进行争抢。
    // tryRelease、 tryReleaseShared: 实际执行资源释放的操作,具体的AQS使用者去实现。

    // 1、 如何判断一个资源的拥有者
    public volatile AtomicReference<Thread> owner = new AtomicReference<>();
    // 保存 正在等待的线程
    public volatile LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();
    // 记录资源状态
    public volatile AtomicInteger state = new AtomicInteger(0);

    // 共享资源占用的逻辑,返回资源的占用情况
    public int tryAcquireShared(){
        throw new UnsupportedOperationException();
    }

    public void acquireShared(){
        boolean addQ = true;
        while(tryAcquireShared() < 0) {
            if (addQ) {
                // 没拿到锁,加入到等待集合
                waiters.offer(Thread.currentThread());
                addQ = false;
            } else {
                // 阻塞 挂起当前的线程,不要继续往下跑了
                LockSupport.park(); // 伪唤醒,就是非unpark唤醒的
            }
        }
        waiters.remove(Thread.currentThread()); // 把线程移除
    }

    public boolean tryReleaseShared(){
        throw new UnsupportedOperationException();
    }

    public void releaseShared(){
        if (tryReleaseShared()) {
            // 通知等待者
            Iterator<Thread> iterator = waiters.iterator();
            while (iterator.hasNext()) {
                Thread next = iterator.next();
                LockSupport.unpark(next); // 唤醒
            }
        }
    }
    
    public AtomicInteger getState() {
        return state;
    }

    public void setState(AtomicInteger state) {
        this.state = state;
    }
}
// 自定义的信号量实现
public class NeteaseSemaphore {
    NeteaseAqs aqs = new NeteaseAqs() {
        @Override
        public int tryAcquireShared() { // 信号量获取, 数量 - 1
            for(;;) {
                int count =  getState().get();
                int n = count - 1;
                if(count <= 0 || n < 0) {
                    return -1;
                }
                if(getState().compareAndSet(count, n)) {
                    return 1;
                }
            }
        }

        @Override
        public boolean tryReleaseShared() { // state + 1
            return getState().incrementAndGet() >= 0;
        }
    };

    /** 许可数量 */
    public NeteaseSemaphore(int count) {
        aqs.getState().set(count); // 设置资源的状态
    }

    public void acquire() {
        aqs.acquireShared();
    } // 获取令牌

    public void release() {
        aqs.releaseShared();
    } // 释放令牌
}
  • 代码中具体使用
// 信号量机制
public class SemaphoreDemo {
    public static void main(String[] args) {
        SemaphoreDemo semaphoreTest = new SemaphoreDemo();
        int N = 9;            // 客人数量
        NeteaseSemaphore semaphore = new NeteaseSemaphore(5); // 手牌数量,限制请求数量
        for (int i = 0; i < N; i++) {
            String vipNo = "vip-00" + i;
            new Thread(() -> {
                try {
                    semaphore.acquire(); // 获取令牌

                    semaphoreTest.service(vipNo);

                    semaphore.release(); // 释放令牌
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

    // 限流 控制5个线程 同时访问
    public void service(String vipNo) throws InterruptedException {
        System.out.println("楼上出来迎接贵宾一位,贵宾编号" + vipNo + ",...");
        Thread.sleep(new Random().nextInt(3000));
        System.out.println("欢送贵宾出门,贵宾编号" + vipNo);
    }

}

CountDownLatch

jdk1.5被引入的一个工具类,常被称为:倒计数器。
创建对象时,引入指定数值作为线程参与的数量;
await: 方法等待计数器值变为0,在这之前,线程进入等待状态;
countdown: 计数器数值减一,直到为0;
经常用于等待其他线程执行到某一节点,在继续执行当前线程代码

  • 代码示例
public class CountDownDemo {
    
    CustomAqs aqSdemo = new CustomAqs() {
        @Override
        public int tryAcquireShared() { // 如果非等于0,代表当前还有线程没准备就绪,则认为需要等待
            return this.getState().get() == 0 ? 1 : -1;
        }

        @Override
        public boolean tryReleaseShared() { // 如果非等于0,代表当前还有线程没准备就绪,则不会通知继续执行
            return this.getState().decrementAndGet() == 0;
        }
    };

    public CountDownDemo(int count) {
        aqSdemo.setState(new AtomicInteger(count));
    }

    public void await() {
        aqSdemo.acquireShared();
    }

    public void countDown() {
        aqSdemo.releaseShared();
    }

    public static void main(String[] args) throws InterruptedException {
        // 一个请求,后台需要调用多个接口 查询数据
        //CountDownLatch cdLdemo = new CountDownLatch(10); // 创建,计数数值
        CountDownDemo countDownDemo = new CountDownDemo(10);
        for (int i = 0; i < 10; i++) { // 启动九个线程,最后一个两秒后启动
            int finalI = i;
            new Thread(() -> {
                try {
                    Thread.sleep(2000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("我是" + Thread.currentThread() + ".我执行接口-" + finalI +"调用了");
                //cdLdemo.countDown(); // 参与计数
                countDownDemo.countDown();
                // 不影响后续操作
            }).start();
        }

        //cdLdemo.await(); // 等待计数器为0
        countDownDemo.await();
        System.out.println("全部执行完毕.我来召唤神龙");

    }
}

  • 执行结果
我是Thread[Thread-3,5,main].我执行接口-3调用了
我是Thread[Thread-1,5,main].我执行接口-1调用了
我是Thread[Thread-6,5,main].我执行接口-6调用了
我是Thread[Thread-0,5,main].我执行接口-0调用了
我是Thread[Thread-2,5,main].我执行接口-2调用了
我是Thread[Thread-4,5,main].我执行接口-4调用了
我是Thread[Thread-5,5,main].我执行接口-5调用了
我是Thread[Thread-9,5,main].我执行接口-9调用了
我是Thread[Thread-8,5,main].我执行接口-8调用了
我是Thread[Thread-7,5,main].我执行接口-7调用了
全部执行完毕.我来召唤神龙

CycliBarrier

jdk1.5被引入.又称线程栅栏。
创建对象时,指定栅栏的线程数量。
await: 等指定数量的线程都处于等待状态时,继续执行后续代码
barrierAction: 线程数量到了指定量之后,自动触发执行的代码。

  • 代码示例
// 循环屏障(栅栏),示例:数据库批量插入
// 游戏大厅... 5人组队打副本
public class CyclicBarrierTest {
    public static void main(String[] args) throws InterruptedException {
        LinkedBlockingQueue<String> sqls = new LinkedBlockingQueue<>();
        // 任务1+2+3...1000  拆分为100个任务(1+..10,  11+20) -> 100线程去处理。

        // 每当有4个线程处于await状态的时候,则会触发barrierAction执行
        CyclicBarrier barrier = new CyclicBarrier(4, new Runnable() {
            @Override
            public void run() {
                // 这是每满足4次数据库操作,就触发一次批量执行
                System.out.println("有4个线程执行了,开始批量插入: " + Thread.currentThread());
                for (int i = 0; i < 4; i++) {
                    System.out.println(sqls.poll());
                }
            }
        });

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    sqls.add("data - " + Thread.currentThread()); // 缓存起来
                    Thread.sleep(1000L); // 模拟数据库操作耗时
                    barrier.await(); // 等待栅栏打开,有4个线程都执行到这段代码的时候,才会继续往下执行
                    System.out.println(Thread.currentThread() + "插入完毕");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }

        Thread.sleep(2000);
    }
}
  • 执行结果
4个线程执行了,开始批量插入: Thread[Thread-2,5,main]
data - Thread[Thread-1,5,main]
data - Thread[Thread-3,5,main]
data - Thread[Thread-0,5,main]
data - Thread[Thread-4,5,main]
Thread[Thread-2,5,main]插入完毕
有4个线程执行了,开始批量插入: Thread[Thread-6,5,main]
data - Thread[Thread-5,5,main]
data - Thread[Thread-6,5,main]
data - Thread[Thread-7,5,main]
data - Thread[Thread-2,5,main]
Thread[Thread-6,5,main]插入完毕
Thread[Thread-9,5,main]插入完毕
Thread[Thread-0,5,main]插入完毕
Thread[Thread-7,5,main]插入完毕
Thread[Thread-8,5,main]插入完毕
Thread[Thread-5,5,main]插入完毕
Thread[Thread-4,5,main]插入完毕
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值