并发编程系列学习笔记08(AQS & JUC)

J.U.C ->java.util.concurrent

  • AQS原理 -> AbstractQueuedSynchronizer

    • 抽象队列同步器,阻塞式锁

    • 其他相关同步器工具的基础框架

    • 特点

      • 用state标识资源状态
      • 状态分独占与共享两个模式
      • 提供FIFO的等待队列
      • 等待唤醒支持多条件变量
    • 自己基于AQS实现一把不可重入锁

/**
 * @author 钦尘
 * @date 2021/8/7 17:31
 * @description 自己以AQS,实现一个不可重入锁
 */
@Slf4j(topic = "TestAqs")
public class TestAqs {

    public static void main(String[] args) {
        MyLock lock = new MyLock();

        new Thread(()->{
            lock.lock();
            try {
                log.info("加锁中");
                Sleeper.sleep(1);
            } finally {
                log.info("解锁中");
                lock.unlock();
            }
        }, "t1").start();

        new Thread(()->{
            lock.lock();
            try {
                log.info("加锁中");
            } finally {
                log.info("解锁中");
                lock.unlock();
            }
        }, "t2").start();
    }

}


@Slf4j
class MyLock implements Lock {

    /**
     * 独占锁
     */
    class MySync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            if(compareAndSetState(0, 1)) {
                log.info("加锁成功");
                // 设置 owner 为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        /**
         * 是否持有锁
         * @return
         */
        @Override
        protected boolean isHeldExclusively() {

            return getState() == 1;
        }

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

    private MySync sync = new MySync();

    /**
     * 加锁
     */
    @Override
    public void lock() {
        sync.acquire(1);
    }

    /**
     * 加锁,可打断
     * @throws InterruptedException
     */
    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    /**
     * 尝试加锁(一次)
     * @return
     */
    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    /**
     * 带超时时间的尝试加锁
     * @param time
     * @param unit
     * @return
     * @throws InterruptedException
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }

    /**
     * 解锁
     */
    @Override
    public void unlock() {
        sync.release(1);
    }

    /**
     * 条件变量
     * @return
     */
    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }
}
  • 理解ReentrantLock原理

    • 非公平加锁与解锁原理

    • 可重入原理

      • state++ 与 --
    • 可打断原理

      • 重置打断标记,抛出异常
    • 公平锁原理

      • 先检查AQS是否存在前驱节点,没有才去竞争
    • 条件变量原理

      • 设置Node 状态= -2
      • signal逻辑
  • 读写锁

    • 读写锁:ReentrantReadWriteLock

    • 数据库数据缓存存在的问题

    • 存在缓存失效或缓存数据不一致问题

    • 应用读写锁+双检机制进行解决

    • 适合读多写少场景,不适合分布式场景

    • 核心原理:将state分为高低16位,给对应读锁和写锁

    • 读写锁:StampedLock,JDK1.8加入

      • 进一步优化读性能
      • 的特点是在使用读锁、写锁时都必须配合【戳】使用加解读锁
      • 支持乐观读
      • 不支持条件变量
      • 不支持可重入
@Slf4j
class DataContainer {

    private int data;
    private final StampedLock lock = new StampedLock();

    public DataContainer(int data) {
        this.data = data;
    }

    public int read(int readTime){
        long stamp = lock.tryOptimisticRead();
        if(lock.validate(stamp)) {
            // 执行读操作
            log.debug("read finish ... {}", stamp);
            return data;
        }
        // 锁升级
        log.debug("updating to read lock....");
        try {
            stamp = lock.readLock();
            log.info("读取完毕");
            return data;
        } finally {
            lock.unlockRead(stamp);
        }
    }
}
  • 信号量:Semaphore

    • 用来限制能同时访问共享资源的线程上限

    • 注意,限制的是线程个数,而非资源个数

    • 资源数量限制可以对比Tomcat LimitLatch实现

    • 可以考虑使用Semaphore优化之前编写的数库连接池

    • 原理

      • 类比停车场,permits就是车位数量
      • 线程获得permits,停车位空余车位就减一
      • 未获得到permits的在车库外面等待
      • 唤醒:由前驱节点唤醒后继节点
/**
 * @author 钦尘
 * @date 2021/8/15 22:04
 * @description 信号量,用于限制同一资源的线程访问数量上线
 */
@Slf4j
public class TestSemaphore {

    public static void main(String[] args) {

        // 第二个参数:公平非公平
        Semaphore semaphore = new Semaphore(3, true);

        // 可以创建10个线程,同时运行
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    // 获得许可
                    semaphore.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                try {
                    log.info("运行中");
                    Sleeper.sleep(1);
                    log.info("运行完毕");
                } finally {
                    // 释放
                    semaphore.release();
                }
            }).start();
        }
    }

}
  • 线程同步协作器:CountdownLatch

    • 倒计时锁,用来等待所有线程完成倒计时
    • await() 用来等待计数归零
    • countDown() 用来让计数减一
    • 可配合线程池用
    • 案例1:模拟10个王者荣耀玩家加载游戏
    • 案例2:远程调用多个外部Rest API接口
    • 需要拿到多个线程运行的结果集合,使用Future更为合适
    private static void try1() throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);

        new Thread(()->{
            Sleeper.sleep(1);
            latch.countDown();
            log.info("end {}", latch.getCount());
        }).start();

        new Thread(()->{
            Sleeper.sleep(2);
            latch.countDown();
            log.info("end {}", latch.getCount());
        }).start();

        new Thread(()->{
            Sleeper.sleep(1);
            latch.countDown();
            log.info("end {}", latch.getCount());
        }).start();

        log.info("主线程等待");
        latch.wait();
        log.info("主线程等待结束,继续执行");
    }
  • 循环珊栏:CyclicBarrier

    • 用来等待线程满足某个计数
    • 与CountdownLatch非常类似,区别在于可重用,计数器可以重置回某个值
    • 注意线程数需与计数必须一致
// CyclicBarrier barrier = new CyclicBarrier(2);
// 需要拿到结果?
CyclicBarrier barrier = new CyclicBarrier(2, ()->{
    log.info("task1 task2 已运行完毕");
});

barrier.await();
  • 线程安全集合类

    • 遗留

      • Hashtable
      • Vector
    • 修饰

      • 使用 Collections 装饰的线程安全集合
      • SynchronizedMap
      • SynchronizedList
      • SynchronizedSet
      • ……
    • JUC

      • Blocking

        • 大部分实现基于锁,并提供用来阻塞的方法
      • CopyOnWrite

        • 容器修改开销相对较重
        • 适合读多写少场景
      • Concurrent

        • 内部很多操作使用 cas 优化,一般可以提供较高吞吐量
        • 缺点:弱一致性,主要体现在遍历、取size等
        • 非线程安全的容器,一般在遍历时被修改,则会抛出异常
    • ConcurrentHashMap

      • 案例:统计多个文件中单词字母数量
      • map.computeIfAbsent(word, (key) -> new LongAdder())
      • computeIfAbsent可保证get / put的原子性
      • 回顾HashMap原理:桶、链表、扩容,多线程并发扩容,JDK7存在严重的并发死链问题
      • JDK8对扩容算法做了调整,不再将头元素加入链表头,而是与扩容前顺序保持一致,解决了死链问题,又会带入扩容丢数据等问题
      • 构造器:懒惰初始化(首次使用才创建)
    • LinkedBlockingQueue

      • 链表阻塞队列
      • 入队核心逻辑:enqueue(Node)
      • 出队核心逻辑:dequeue()
      • 加锁精髓:用了两把锁+dummy节点实现,对应省消费者和生产者两个线程和并行执行
    • ConcurrentLinkedQueue

      • 设计与 LinkedBlockingQueue 非常像
      • 两把【锁】,同一时刻,可以允许两个线程同时(一个生产者与一个消费者)执行
      • 两把锁住的是不同对象,避免竞争
      • 【锁】使用了 cas 来实现
    • CopyOnWriteArrayList

      • 底层实现采用写时拷贝思想
      • 增删改操作会将底层数组拷贝一份
      • 更改操作在新数组上执行
      • 读写分离
      • get获取元素存在弱一致性问题
      • 弱一致性:并不代表不好,MySQL的MVCC就是已弱一致性的表现,高并发与强一致性永远是矛盾的

扩展了解:第三方框架

  • disruptor
  • guava->Ratelimit
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值