1 锁机制

1.1 [0]分类:3种

01.常用信息1
    a.全局锁
        flush tables with read lock / unlock tables
    b.表级锁
        a.表锁
            lock tables t read / write
        b.元数据锁(MDL)
            CURD操作自动加MDL读锁
            表结构变更自动加MDL写锁
        c.意向锁
            select ... lock in share mode
            select ... for update
        d.AUTO-INC锁
            主键字段声明AUTO_INCREMENT属性,插入数据时加锁
    c.行级锁
        a.记录锁
            select * from t where id = 1 for update
        b.间隙锁
            select * from t where age > 1 and age < 10 for update
        c.Next-Key Lock
            select * from t where age = 10 for update (age为非唯一索引)

02.常用信息2
    a.总结
        a.锁粒度
            表锁:【SQL语句没有匹配到索引时,使用表锁】
            行锁:【行级锁增删改查匹配到索引时,使用行锁】,默认InnoDB使用行锁
        b.兼容性
            共享锁(读锁):不堵塞
            排他锁(写锁):堵塞
        c.加锁机制
            悲观锁:读多写少
            乐观锁:读少写多
    b.分类1
        a.锁粒度
            表锁【SQL语句没有匹配到索引时,使用表锁】:开销小,加锁快;锁定力度大,发生锁冲突概率高,并发度最低;不会出现死锁。
            行锁【行级锁增删改查匹配到索引时使用行锁】(默认):开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高。
            页锁:开销和加锁速度介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度一般
        b.兼容性
            共享锁(S Lock),也叫读锁(read lock),相互不阻塞。
            排他锁(X Lock),也叫写锁(write lock),排它锁是阻塞的,在一定时间内,只有一个请求能执行写入,并阻止其它锁读取正在写入的数据。
        c.加锁机制
            悲观锁:读多写少
            乐观锁:读少写多
    c.分类2
        a.锁粒度
            表锁(悲观锁):系统开销最小,会锁定整张表,MyIsam使用表锁
            行锁(悲观锁)(默认):最大程度的支持并发处理,但是也带来了最大的锁开销,InnoDB使用行锁
        b.兼容性
            共享锁(读锁):不堵塞,多个用户可以同时读一个资源,互不干扰。
            排他锁(写锁):一个写锁会阻塞其他的读锁和写锁,这样可以只允许一个用户进行写入,防止其他用户读取正在写入的资源。
        c.加锁机制
            悲观锁:读多写少
            乐观锁:读少写多

03.常用信息3
    a.乐观锁、悲观锁
        a.依据
            并发冲突
        b.概念
            乐观锁:假设冲突少,提交时检查
            悲观锁:假设冲突多,操作前获取锁
        c.实现
            乐观锁:AtomicStampedReference
            悲观锁:synchronized、ReentrantLock、ReentrantReadWriteLock、StampedLock
    b.自旋锁、适应性自旋锁
        a.依据
            自旋次数控制
        b.概念
            自旋锁:不断尝试获取锁
            适应性自旋锁:自旋次数动态调整
        c.实现
            自旋锁:AtomicBoolean
            适应性自旋锁:JVM 内部实现
    c.无锁、偏向锁、轻量级锁、重量级锁
        a.依据
            锁的实现机制和开销
        b.概念
            无锁:原子操作
            偏向锁:优化单线程持有锁
            轻量级锁:CAS操作
            重量级锁:互斥量
        c.实现
            无锁:AtomicInteger
            偏向锁、轻量级锁、重量级锁:JVM内部实现synchronized
    d.可重入锁、非可重入锁
        a.依据
            同一线程是否可多次获取同一锁
        b.概念
            可重入锁:同一线程可多次获取
            非可重入锁:同一线程只能获取一次
        c.实现
            可重入锁:ReentrantLock、synchronized
            非可重入锁:Semaphore(单许可)
    e.公平锁、非公平锁
        a.依据
            获取锁的顺序控制
        b.概念
            公平锁:按顺序获取锁
            非公平锁:不保证顺序,提高吞吐量
        c.实现
            公平锁:ReentrantLock(true)
            非公平锁:ReentrantLock(默认)
    f.读写锁、排它锁
        a.依据
            读写操作的并发性
        b.概念
            读写锁:多个读线程并发,写线程独占
            排它锁:读写线程都独占
        c.实现
            读写锁:ReentrantReadWriteLock
            排它锁:ReentrantLock、synchronized

04.常用信息4
    a.示例
        lock (locker)//加锁
        {
            apple -= 1;
            Console.WriteLine(name + "正在吃苹果");
            Thread.Sleep(3000);
            Console.WriteLine(name + "吃完了,还剩" + apple + "个苹果\n");
            if (apple <= 1)//变为1 不然会吃-1个苹果
                break;
        }
    b.为什么传入一个私有的、静态的、只读的locker对象,就能完成锁定?
        a.私有 (private)
            为了防止外部代码访问,避免不必要的阻塞和死锁。使用“门钥匙”的比喻。
            防止外部干扰。确保这个锁只在类的内部使用,避免其他不相关的代码无意中锁定了它,
            从而引起性能问题或死锁。这就像你家的门钥匙只由你保管,不会被外人乱用。
        b.静态 (static)
            为了匹配静态资源的生命周期,保证所有线程使用的是同一个锁实例。
            保证锁的全局唯一性,用来保护同样是全局唯一的静态资源(如你的 apple 数量)。
            所有线程,无论通过哪个实例访问,都在竞争同一把锁,这样才能有效同步。
        c.只读 (readonly)
            为了保证锁对象引用不会改变,否则锁就会失效。
            防止锁对象被替换。确保这把“锁”本身不会被意外地换掉。
            如果锁可以被改变,不同线程可能会锁定不同的对象,导致锁机制失效,多个线程同时进入临界区。

1.2 [1]死锁:定义

01.定义
    两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
    这是一个严重的问题,因为死锁会让你的程序挂起无法完成任务,

1.3 [1]死锁:原因

01.常见原因
    a.系统资源不足
        略
    b.多线程的执行顺序不合理
        a.算法问题
            解决死锁可以通过算法解决:银行家算法
        b.打破四种必要条件
            互斥条件:一个资源每次只能被一个进程使用
            请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
            不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺
            循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

1.4 [1]死锁:四个条件

01.四个条件
    互斥条件:一个资源每次只能被一个进程使用。
    请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
    不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
    循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

1.5 [1]死锁:避免手段

01.避免手段
    a.避免大事务
        大事务占据锁的时间长,将大事务拆分成多个小事务快速释放锁,可降低死锁产生的概率和避免冲突
    b.减少锁粒度
        使用细粒度锁或非阻塞数据结构(如ConcurrentHashMap)
    c.调整申请锁的顺序
        统一锁的获取顺序,避免循环等待
        在更新数据的时候要保证获得足够的锁,举个例子:先获取影响范围大的锁
        比如说修改操作,先将排他锁获取到,再获取共享锁。或固定顺序访问数据,这样也能避免死锁的情况
    d.更改数据库隔离级别
        可重复读比读已提交多了间隙锁和临键锁,利用读已提交替换之可降低死锁的情况
    e.合理建立索引,减少加锁范围
        如果命中索引,则会锁对应的行,不然就是全表行都加锁,这样冲突大,死锁的概率就高了
    f.超时机制
        通过tryLock或Semaphore设置超时,避免无限等待
    g.开启死锁检测,适当调整锁等待时长
        略

1.6 [1]死锁:解决手段

00.汇总
    a.锁顺序不一致
        当多个线程以不同的顺序获取同一组锁时,可能形成循环等待
        例如,线程1先获取锁A再获取锁B,线程2先获取锁B再获取锁A,最终互相等待
    b.动态锁顺序死锁
        锁的顺序由外部参数动态决定。例如,转账时根据账户参数顺序获取锁,不同线程传入相反顺序的账户
    c.协作对象间的死锁
        在持有锁时调用外部方法,而外部方法可能获取其他锁,形成嵌套死锁
    d.资源池死锁
        多个线程竞争有限资源,每个线程持有部分资源并等待其他线程释放,形成循环等待

01.锁顺序不一致(Lock Ordering Deadlock)
    a.原因分析
        当多个线程以不同的顺序获取同一组锁时,可能形成循环等待
        例如,线程1先获取锁A再获取锁B,线程2先获取锁B再获取锁A,最终互相等待
    b.代码示例
        package com.dereksmart.crawling.lock;
        public class LockOrderDeadlock {
            private final Object lockA = new Object();
            private final Object lockB = new Object();
            public void method1() {
                synchronized (lockA) {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Method1 acquired lockA");
                    synchronized (lockB) {
                        System.out.println("Method1 acquired lockB");
                    }
                }
            }
            public void method2() {
                synchronized (lockB) {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Method2 acquired lockB");
                    synchronized (lockA) {
                        System.out.println("Method2 acquired lockA");
                    }
                }
            }
            public static void main(String[] args) {
                LockOrderDeadlock deadlock = new LockOrderDeadlock();
                new Thread(deadlock::method1).start();
                new Thread(deadlock::method2).start();
            }
        }
    c.复现死锁
        运行此程序,两个线程卡在获取第二个锁的位置,导致死锁。通过 jps、jstack 指令就可发现死锁:
        通过上面截图可以发现第一个线程锁住了<0x000000076c6f80b8> 等待<0x000000076c6f80c8> 而第二个线程是锁住了<0x000000076c6f80c8>等待<0x000000076c6f80b8>
    d.解决方案
        统一锁的获取顺序。例如,按锁对象的哈希值排序
        public void fixedMethod(Object a, Object b) {
            Object first = System.identityHashCode(a) < System.identityHashCode(b) ? a : b;
            Object second = (first == a) ? b : a;
            synchronized (first) {
                System.out.println(Thread.currentThread().getName() + " acquired lock on " + first);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (second) {
                    System.out.println(Thread.currentThread().getName() + " acquired lock on " + second);
                    System.out.println(Thread.currentThread().getName() + " is executing business logic.");
                }
            }
        }
        public static void main(String[] args) throws InterruptedException {
            LockOrderDeadlock deadlock = new LockOrderDeadlock();
            Thread t1 = new Thread(() -> deadlock.fixedMethod(deadlock.lockA, deadlock.lockB), "Thread-1");
            Thread t2 = new Thread(() -> deadlock.fixedMethod(deadlock.lockB, deadlock.lockA), "Thread-2");
            t1.start();
            t2.start();
            t1.join();
            t2.join();
        }

02.动态锁顺序死锁(Dynamic Lock Order Deadlock)
    a.原因分析
        锁的顺序由外部参数动态决定。例如,转账时根据账户参数顺序获取锁,不同线程传入相反顺序的账户
    b.代码示例
        package com.dereksmart.crawling.lock;
        public class DynamicLockOrderDeadlock {
            static class Account {
                private int balance;
                public void transfer(Account to, int amount) {
                    synchronized (this) {
                        System.out.println(Thread.currentThread().getName() + "get this lock");
                        try {
                            Thread.sleep(2000);
                            synchronized (to) {
                                System.out.println(Thread.currentThread().getName() + "get to lock");
                                Thread.sleep(2000);
                                if (this.balance >= amount) {
                                    this.balance -= amount;
                                    to.balance += amount;
                                }
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            public static void main(String[] args) {
                Account a = new Account();
                Account b = new Account();
                new Thread(() -> a.transfer(b, 100)).start();
                new Thread(() -> b.transfer(a, 200)).start(); // 反向调用导致死锁
            }
        }
    c.解决方案
        将所有需要锁定的对象集中管理,并一次性获取所有必要的锁。这种方法可以避免因不同线程以不同顺序请求相同资源而导致的死锁
        package com.dereksmart.crawling.lock;
        import java.util.concurrent.locks.Lock;
        import java.util.concurrent.locks.ReentrantLock;
        public class DynamicLockOrderDeadlock1 {
            static class Account {
                private int balance;
                private final Lock lock = new ReentrantLock();
                public void transfer(Account to, int amount) {
                    LockManager.acquireLocks(this, to);
                    try {
                        System.out.println(Thread.currentThread().getName() + " get locks on both accounts");
                        if (this.balance >= amount) {
                            this.balance -= amount;
                            to.balance += amount;
                        }
                    } finally {
                        LockManager.releaseLocks(this, to);
                    }
                }
            }
            public static void main(String[] args) {
                Account a = new Account();
                Account b = new Account();
                new Thread(() -> a.transfer(b, 100)).start();
                new Thread(() -> b.transfer(a, 200)).start(); // 反向调用不会导致死锁
            }
            static class LockManager {
                public static void acquireLocks(Account from, Account to) {
                    while (true) {
                        boolean fromLock = false;
                        boolean toLock = false;
                        try {
                            fromLock = from.lock.tryLock();
                            toLock = to.lock.tryLock();
                        } finally {
                            if (fromLock && toLock) {
                                return;
                            }
                            if (fromLock) {
                                from.lock.unlock();
                            }
                            if (toLock) {
                                to.lock.unlock();
                            }
                        }
                        try {
                            Thread.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
                public static void releaseLocks(Account from, Account to) {
                    from.lock.unlock();
                    to.lock.unlock();
                }
            }
        }

03.协作对象间的死锁(Cooperation Object Deadlock)
    a.原因分析
        在持有锁时调用外部方法,而外部方法可能获取其他锁,形成嵌套死锁
    b.代码示例
        package com.dereksmart.crawling.lock;
        public class CooperationObjectDeadlock {
            static class Resource {
                private final String name;
                public Resource(String name) {
                    this.name = name;
                }
                public synchronized void cooperate(Resource other) {
                    System.out.println(Thread.currentThread().getName() + " locked " + this.name);
                    try {
                        Thread.sleep(50);
                        other.finish();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                public synchronized void finish() {
                    System.out.println(Thread.currentThread().getName() + " finished " + this.name);
                }
            }
            public static void main(String[] args) {
                Resource resourceA = new Resource("ResourceA");
                Resource resourceB = new Resource("ResourceB");
                Thread thread1 = new Thread(() -> resourceA.cooperate(resourceB), "Thread1");
                Thread thread2 = new Thread(() -> resourceB.cooperate(resourceA), "Thread2");
                thread1.start();
                thread2.start();
            }
        }
    c.解决方案:使用开放调用(避免在同步代码块中调用外部方法)
        package com.dereksmart.crawling.lock;
        import java.util.concurrent.locks.Lock;
        import java.util.concurrent.locks.ReentrantLock;
        public class CooperationObjectDeadlockSolution {
            static class Resource {
                private final String name;
                private final Lock lock = new ReentrantLock();
                public Resource(String name) {
                    this.name = name;
                }
                public void cooperate(Resource other) {
                    while (true) {
                        boolean myLock = false;
                        boolean otherLock = false;
                        try {
                            myLock = this.lock.tryLock();
                            otherLock = other.lock.tryLock();
                            if (myLock && otherLock) {
                                System.out.println(Thread.currentThread().getName() + " locked " + this.name + " and " + other.name);
                                try {
                                    Thread.sleep(50);
                                    other.finish();
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                                return;
                            }
                        } finally {
                            if (myLock) {
                                this.lock.unlock();
                            }
                            if (otherLock) {
                                other.lock.unlock();
                            }
                        }
                        try {
                            Thread.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
                public void finish() {
                    lock.lock();
                    try {
                        System.out.println(Thread.currentThread().getName() + " finished " + this.name);
                    } finally {
                        lock.unlock();
                    }
                }
            }
            public static void main(String[] args) {
                Resource resourceA = new Resource("ResourceA");
                Resource resourceB = new Resource("ResourceB");
                Thread thread1 = new Thread(() -> resourceA.cooperate(resourceB), "Thread1");
                Thread thread2 = new Thread(() -> resourceB.cooperate(resourceA), "Thread2");
                thread1.start();
                thread2.start();
            }
        }
    d.代码说明
        a.Resource类
            每个 Resource 对象都有一个 ReentrantLock 实例,用于锁定资源
            cooperate 方法使用 tryLock 方法尝试获取两个资源的锁。如果不能同时获取两个锁,则释放已获取的锁并重试,避免死锁
            finish 方法在操作完成后释放锁
        b.主方法
            创建两个 Resource 对象,并启动两个线程分别进行协作操作
            由于使用了 tryLock 方法,两个线程会以非阻塞的方式尝试获取锁,从而避免死锁

04.资源池死锁(Resource Pool Deadlock)
    a.原因分析
        多个线程竞争有限资源,每个线程持有部分资源并等待其他线程释放,形成循环等待
    b.代码示例
        package com.dereksmart.crawling.lock;
        import java.util.ArrayList;
        import java.util.List;
        import java.util.concurrent.Semaphore;
        public class ResourcePoolDeadlock {
            private static final int POOL_SIZE = 2;
            private final Semaphore semaphore = new Semaphore(POOL_SIZE, true);
            private final List<Object> resources = new ArrayList<>();
            public ResourcePoolDeadlock() {
                for (int i = 0; i < POOL_SIZE; i++) {
                    resources.add(new Object());
                }
            }
            public Object acquire() throws InterruptedException {
                semaphore.acquire();
                synchronized (resources) {
                    Thread.sleep(2000);
                    return resources.remove(0);
                }
            }
            public void release(Object resource) {
                synchronized (resources) {
                    resources.add(resource);
                }
                semaphore.release();
            }
            public static void main(String[] args) {
                ResourcePoolDeadlock pool = new ResourcePoolDeadlock();
                new Thread(() -> {
                    try {
                        Object res1 = pool.acquire();
                        Object res2 = pool.acquire();
                    } catch (InterruptedException e) { /* ... */ }
                }).start();
            }
        }
    c.解决方案:使用超时机制或按顺序分配资源
        public Object acquireWithTimeout(long timeout) throws InterruptedException {
            if (!semaphore.tryAcquire(timeout, TimeUnit.MILLISECONDS)) {
                throw new TimeoutException();
            }
            synchronized (resources) {
                return resources.remove(0);
            }
        }

1.7 [1]死锁、死循环

00.汇总
    死循环:导致CPU使用率升高
    死锁:不会导致CPU使用率升高,甚至可能降低;死锁后业务代码无法继续运行,导致使用率降低

01.死循环
    a.定义
        死循环是指程序中某个循环没有退出条件或退出条件永远不满足,导致程序不断重复执行循环体
    b.影响
        CPU使用率升高:死循环会导致CPU使用率升高,因为涉及的线程会不断执行循环中的指令而不会退出,持续占用处理器资源
        原因:CPU会花费所有可用的时间片去执行这个无尽的循环,导致几乎没有资源剩余来处理其他任务或线程
    c.实验验证
        编写一个简单的死循环代码,输出系统时间戳
        运行代码后,观察CPU使用率,发现用户态(us)和内核态(sy)使用率均升高
        如果注释掉输出代码,只有用户态的使用率升高,因为不再有系统调用进入内核态

02.死锁
    a.定义
        死锁是指两个或多个线程在等待对方持有的锁,导致所有线程都无法继续执行
    b.影响
        CPU使用率不升高,甚至可能降低:死锁发生时,涉及的线程会在等待获取锁时被挂起,而不是处于忙碌等待状态,因此不会占用CPU资源进行计算
        原因:线程在等待状态,不会占用CPU资源,导致CPU使用率不会升高
    c.实验验证
        编写一个产生死锁的代码,创建多个线程来模拟死锁
        运行代码后,观察CPU使用率,发现没有明显升高,Java进程占用的CPU非常低
        使用 `jstack` 查看线程状态,发现线程处于睡眠状态(S),因为拿不到锁让出CPU的使用权

1.8 [2]数据库锁:死锁

01.死锁
    死锁是指两个或多个事务在持有锁的情况下,等待其他事务释放锁,从而导致事务无法继续执行的现象
    死锁会导致数据库操作无法完成,影响系统的稳定性和性能

02.原理
    a.锁互斥
        事务持有锁并等待其他事务释放锁
    b.循环等待
        多个事务形成循环等待链,导致死锁
    c.锁类型
        包括行锁、间隙锁、插入意向锁等,影响死锁的发生

03.常用API
    a.SHOW ENGINE INNODB STATUS
        查看InnoDB引擎的状态,包括死锁信息
    b.SHOW PROCESSLIST
        查看当前正在执行的线程,帮助识别死锁
    c.EXPLAIN
        分析SQL语句的执行计划,帮助优化锁使用

04.使用步骤
    a.识别死锁
        通过查看死锁日志和InnoDB状态识别死锁
    b.分析死锁原因
        分析事务持有的锁和等待的锁,识别死锁的根本原因
    c.优化锁使用
        调整SQL语句和事务逻辑,避免死锁
    d.监控和预防
        使用监控工具和优化建议,预防死锁的发生

05.场景及代码示例
    a.场景1:唯一索引并发写入回滚
        a.代码
            -- Session A
            START TRANSACTION;
            INSERT INTO device (id, use_times) VALUES (1, 10);
            ROLLBACK;

            -- Session B
            START TRANSACTION;
            INSERT INTO device (id, use_times) VALUES (1, 20);
            -- 等待Session A释放锁

            -- Session C
            START TRANSACTION;
            INSERT INTO device (id, use_times) VALUES (1, 30);
            -- 等待Session A释放锁
        b.说明
            Session A插入后回滚,Session B和C等待锁释放,形成死锁
    b.场景2:唯一索引并发删除插入
        a.代码
            -- Session A
            START TRANSACTION;
            DELETE FROM device WHERE id = 1;
            COMMIT;

            -- Session B
            START TRANSACTION;
            INSERT INTO device (id, use_times) VALUES (1, 20);
            -- 等待Session A释放锁

            -- Session C
            START TRANSACTION;
            INSERT INTO device (id, use_times) VALUES (1, 30);
            -- 等待Session A释放锁
        b.说明
            Session A删除后,Session B和C插入时等待锁释放,形成死锁
    c.场景3:唯一索引并发删除后插入
        a.代码
            -- Session A
            START TRANSACTION;
            DELETE FROM device WHERE id = 1;

            -- Session B
            START TRANSACTION;
            DELETE FROM device WHERE id = 2;
            -- 等待Session A释放锁

            -- Session A
            INSERT INTO device (id, use_times) VALUES (1, 20);
            -- 等待Session B释放锁
        b.说明
            Session A和B形成循环等待,导致死锁

06.死锁优化建议
    a.避免大事务
        尽量拆分大事务,减少锁持有时间
    b.避免经典死锁模式
        识别常见死锁模式,调整事务逻辑
    c.批量操作排序
        按相同顺序插入或删除,减少锁冲突
    d.使用普通索引
        避免使用唯一索引,减少锁竞争
    e.调整隔离级别
        使用RC隔离级别加binlog_format=row模式,减少锁冲突

1.9 [2]数据库锁:锁表

01.锁表
    a.关键字
        锁表发生在insert update 、delete中
    b.原理
        数据库使用独占式封锁机制,当执行上面的语句时,对表进行锁住,直到发生commite 或者 回滚 或者退出数据库用户
    c.锁表原因
        原因1:A程序执行了对 tableA 的 insert,并还未 commite时,
               B程序也对tableA 进行insert 则此时会发生资源正忙的异常 就是锁表
        原因2:锁表常发生于并发而不是并行
             (并行时,一个线程操作数据库时,另一个线程是不能操作数据库的,cpu 和i/o 分配原则)

02.使用
    a.建议
        减少insert 、update 、delete 语句执行 到 commite 之间的时间。
        具体点批量执行改为单个执行、优化sql自身的非执行速度如果异常对事物进行回滚。
    b.操作
        # 查看进程id,然后用kill id杀掉进程
        show processlist; 或 SELECT * FROM information_schema.PROCESSLIST;
        # 查询正在执行的进程
        SELECT * FROM information_schema.PROCESSLIST where length(info) >0 ;
        # 查询是否锁表
        show OPEN TABLES where In_use > 0;
        # 查看被锁住的
        SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
        # 等待锁定
        SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
        # 杀掉锁表进程
        kill 5601

1.10 [2]数据库锁:表锁

01.定义
    表锁(Table Lock)是MySQL中的一种锁机制,用于锁定整个表
    表锁是MySQL中最粗粒度的锁类型,通常用于MyISAM存储引擎
    表锁可以防止其他事务对表进行并发操作,从而确保数据的一致性

02.原理
    a.目的
        表锁用于确保事务对整个表的独占访问,防止其他事务对该表进行并发修改
    b.类型
        a.读锁(共享锁,S锁)
            允许多个事务同时读取表,但不允许修改
        b.写锁(排他锁,X锁)
            允许一个事务读取和修改表,其他事务不能同时访问
    c.工作方式
        当一个事务需要对表进行操作时,MySQL会在该表上加锁,以确保数据的一致性和完整性

03.常用API
    a.说明
        表锁是通过SQL语句自动管理的
    b.开发者可以通过以下SQL操作来触发表锁
        LOCK TABLES ... READ:对表加读锁
        LOCK TABLES ... WRITE:对表加写锁
        UNLOCK TABLES:释放表锁

04.使用步骤
    a.执行SQL操作
        在需要加表锁的查询中使用上述SQL操作
    b.观察锁行为
        通过查看锁状态和事务行为,理解表锁的应用

05.场景及代码示例
    a.场景1:使用读锁(共享锁,S锁)
        a.假设有一个表`products`,包含以下数据
            CREATE TABLE products (
                product_id INT PRIMARY KEY,
                price DECIMAL(10, 2)
            );
            INSERT INTO products (product_id, price) VALUES (1, 100.00), (2, 200.00);
        b.在事务A中执行以下操作
            LOCK TABLES products READ;
            SELECT * FROM products WHERE product_id = 1;
            -------------------------------------------------------------------------------------------------
            说明:此时,事务A在表`products`上加了读锁(共享锁,S锁),其他事务可以读取但不能修改该表
        c.在事务B中尝试更新
            LOCK TABLES products WRITE;
            UPDATE products SET price = 150.00 WHERE product_id = 1;
            -------------------------------------------------------------------------------------------------
            结果:事务B会被阻塞,直到事务A释放读锁
    b.场景2:使用写锁(排他锁,X锁)
        a.在事务A中执行以下操作
            LOCK TABLES products WRITE;
            UPDATE products SET price = 150.00 WHERE product_id = 1;
            -------------------------------------------------------------------------------------------------
            说明:此时,事务A在表`products`上加了写锁(排他锁,X锁),其他事务不能对该表进行任何操作
        b.在事务B中尝试读取
            LOCK TABLES products READ;
            SELECT * FROM products WHERE product_id = 1;
            -------------------------------------------------------------------------------------------------
            结果:事务B会被阻塞,直到事务A释放写锁

06.注意事项
    a.性能影响
        表锁是最粗粒度的锁,可能导致锁争用,影响并发性能
    b.锁范围
        表锁锁定整个表,而不是特定的行
    c.自动管理
        表锁由MySQL自动管理,开发者无需手动设置

1.11 [2]数据库锁:行锁

01.定义
    行锁(Row Lock)是MySQL InnoDB存储引擎中的一种锁机制,用于锁定表中的特定行
    行锁是MySQL中最细粒度的锁类型,允许多个事务并发地操作同一张表中的不同行,从而提高并发性能

02.原理
    a.目的
        行锁用于确保事务对特定行的独占访问,防止其他事务对该行进行并发修改
    b.类型
        a.共享锁(S锁)
            允许多个事务同时读取一行,但不允许修改
        b.排他锁(X锁)
            允许一个事务读取和修改一行,其他事务不能同时访问
    c.工作方式
        当一个事务需要对某行进行操作时,InnoDB会在该行上加锁,以确保数据的一致性和完整性

03.常用API
    a.说明
        行锁是通过SQL语句自动管理的
    b.开发者可以通过以下SQL操作来触发行锁
        SELECT ... FOR UPDATE             对查询的行加排他锁
        SELECT ... LOCK IN SHARE MODE     对查询的行加共享锁
        UPDATE                            对更新的行加排他锁
        DELETE                            对删除的行加排他锁
        INSERT                            对插入的行加排他锁

04.使用步骤
    a.设置隔离级别
        确保数据库的隔离级别为可重复读(REPEATABLE READ)或读已提交(READ COMMITTED)
    b.执行SQL操作
        在需要加行锁的查询中使用上述SQL操作
    c.观察锁行为
        通过查看锁状态和事务行为,理解行锁的应用

05.场景及代码示例
    a.场景1:使用排他锁(X锁)
        a.假设有一个表`accounts`,包含以下数据
            CREATE TABLE accounts (
                account_id INT PRIMARY KEY,
                balance DECIMAL(10, 2)
            );
            INSERT INTO accounts (account_id, balance) VALUES (1, 1000.00), (2, 2000.00);
        b.在事务A中执行以下操作
            START TRANSACTION;
            SELECT balance FROM accounts WHERE account_id = 1 FOR UPDATE;
            -------------------------------------------------------------------------------------------------
            说明:此时,事务A在`account_id`为1的行上加了排他锁(X锁),其他事务不能对该行进行修改
        c.在事务B中尝试更新
            START TRANSACTION;
            UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
            -------------------------------------------------------------------------------------------------
            结果:事务B会被阻塞,直到事务A提交或回滚
    b.场景2:使用共享锁(S锁)
        a.在事务A中执行以下操作
            START TRANSACTION;
            SELECT balance FROM accounts WHERE account_id = 1 LOCK IN SHARE MODE;
            -------------------------------------------------------------------------------------------------
            说明:此时,事务A在`account_id`为1的行上加了共享锁(S锁),其他事务可以读取但不能修改该行
        b.在事务B中尝试更新
            START TRANSACTION;
            UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
            -------------------------------------------------------------------------------------------------
            结果:事务B会被阻塞,直到事务A提交或回滚

06.注意事项
    a.性能影响
        行锁是最细粒度的锁,允许高并发,但可能导致锁争用
    b.锁范围
        行锁只锁定特定的行,而不是整个表
    c.自动管理
        行锁由MySQL自动管理,开发者无需手动设置

1.12 [2]数据库锁:间隙锁

01.定义
    间隙锁(Gap Lock)是MySQL InnoDB存储引擎中的一种锁机制,用于锁定索引记录之间的间隙
    它是InnoDB的行级锁的一部分,主要用于防止幻读现象的发生

02.原理
    a.目的
        间隙锁的主要目的是防止其他事务在当前事务执行期间插入新的行,从而避免幻读。
    b.工作方式
        间隙锁锁定的是索引记录之间的间隙,而不是具体的行。这意味着即使某个索引记录不存在,间隙锁也可以锁定该位置。
    c.应用场景
        间隙锁通常在可重复读(REPEATABLE READ)隔离级别下使用,以确保事务的一致性。

03.常用API
    a.说明
        间隙锁是MySQL自动管理的,开发者无法直接通过API来设置间隙锁
    b.它通常在以下SQL操作中自动应用
        SELECT ... FOR UPDATE
        SELECT ... LOCK IN SHARE MODE
        UPDATE
        DELETE

04.使用步骤
    a.设置隔离级别
        确保数据库的隔离级别为可重复读(REPEATABLE READ)
    b.执行SQL操作
        在需要防止幻读的查询中使用上述SQL操作
    c.观察锁行为
        通过查看锁状态和事务行为,理解间隙锁的应用

05.场景及代码示例
    a.场景1:防止幻读
        a.假设有一个表`orders`,包含以下数据
            CREATE TABLE orders (
                order_id INT PRIMARY KEY,
                order_date DATE
            );
            INSERT INTO orders (order_id, order_date) VALUES (1, '2023-01-01'), (3, '2023-01-03');
        b.在事务A中执行以下操作
            START TRANSACTION;
            SELECT * FROM orders WHERE order_id BETWEEN 1 AND 3 FOR UPDATE;
            -------------------------------------------------------------------------------------------------
            说明:此时,事务A在`order_id`为1和3之间的间隙上加了间隙锁
        c.在事务B中尝试插入
            INSERT INTO orders (order_id, order_date) VALUES (2, '2023-01-02');
            结果:事务B会被阻塞,因为间隙锁阻止了在1和3之间插入新的记录
    b.场景2:避免插入冲突
        a.假设有一个表`products`,包含以下数据:
            CREATE TABLE products (
                product_id INT PRIMARY KEY,
                price DECIMAL(10, 2)
            );
            INSERT INTO products (product_id, price) VALUES (1, 100.00), (3, 300.00);
        b.在事务A中执行以下操作
            START TRANSACTION;
            SELECT * FROM products WHERE product_id BETWEEN 1 AND 3 LOCK IN SHARE MODE;
            -------------------------------------------------------------------------------------------------
            说明:此时,事务A在`product_id`为1和3之间的间隙上加了间隙锁。
        c.在事务B中尝试插入
            INSERT INTO products (product_id, price) VALUES (2, 200.00);
            -------------------------------------------------------------------------------------------------
            结果:事务B会被阻塞,因为间隙锁阻止了在1和3之间插入新的记录

06.注意事项
    a.性能影响
        间隙锁可能会导致锁争用,影响并发性能
    b.锁范围
        间隙锁锁定的是索引间隙,因此在没有索引的情况下,可能会锁定更多的行
    c.隔离级别
        间隙锁通常在可重复读隔离级别下使用,在读已提交(READ COMMITTED)级别下通常不会使用

1.13 [2]数据库锁:意向锁

01.定义
    意向锁(Intention Lock)是MySQL InnoDB存储引擎中的一种锁机制,用于表级锁和行级锁之间的协调
    意向锁本身并不阻塞任何操作,它的主要作用是表明一个事务计划在某些行上加锁

02.原理
    a.目的
        意向锁用于提高锁定效率,允许多个事务在同一张表上并发操作不同的行
    b.类型
        a.意向共享锁(IS)
            表示事务计划在某些行上加共享锁
        b.意向排他锁(IX)
            表示事务计划在某些行上加排他锁
    c.工作方式
        当一个事务需要在某些行上加共享锁或排他锁时,首先在表级别加上相应的意向锁
        这样可以快速判断是否可以在表上加表级锁,而不需要检查每一行的锁状态

03.常用API
    a.说明
        意向锁是MySQL自动管理的,开发者无法直接通过API来设置意向锁
    b.它通常在以下SQL操作中自动应用
        SELECT ... LOCK IN SHARE MODE:加共享锁
        SELECT ... FOR UPDATE:加排他锁
        UPDATE
        DELETE
        INSERT

04.使用步骤
    a.执行SQL操作
        在需要加行级锁的查询中使用上述SQL操作
    b.观察锁行为
        通过查看锁状态和事务行为,理解意向锁的应用

05.场景及代码示例
    a.场景1:意向共享锁(IS)
        a.假设有一个表`products`,包含以下数据
            CREATE TABLE products (
                product_id INT PRIMARY KEY,
                price DECIMAL(10, 2)
            );
            INSERT INTO products (product_id, price) VALUES (1, 100.00), (2, 200.00);
        b.在事务A中执行以下操作
            START TRANSACTION;
            SELECT * FROM products WHERE product_id = 1 LOCK IN SHARE MODE;
            ---------------------------------------------------------------------------------------------
            说明:此时,事务A在表`products`上加了意向共享锁(IS),并在`product_id`为1的行上加了共享锁
        c.在事务B中尝试加表级排他锁
            LOCK TABLES products WRITE;
            -------------------------------------------------------------------------------------------------
            结果:事务B会被阻塞,因为事务A持有意向共享锁,表明有行级共享锁存在
    b.场景2:意向排他锁(IX)
        a.在事务A中执行以下操作
            START TRANSACTION;
            UPDATE products SET price = 150.00 WHERE product_id = 1;
            -------------------------------------------------------------------------------------------------
            说明:此时,事务A在表`products`上加了意向排他锁(IX),并在`product_id`为1的行上加了排他锁
        b.在事务B中尝试加表级共享锁
            LOCK TABLES products READ;
            -------------------------------------------------------------------------------------------------
            结果:事务B会被阻塞,因为事务A持有意向排他锁,表明有行级排他锁存在

06.注意事项
    a.性能影响
        意向锁本身不阻塞任何操作,但可以快速判断是否可以在表上加表级锁
    b.锁协调
        意向锁用于协调表级锁和行级锁之间的关系,提高锁定效率
    c.自动管理
        意向锁由MySQL自动管理,开发者无需手动设置

1.14 [2]数据库锁:InnoDB引擎

01.InnoDB引擎
    a.结论1
        select. for update, insert,update,delete操作都会触发锁,只要涉及到表的改动,都会触发锁;
    b.结论2
        表中有索引,并不代表触发的锁就是行锁;
        当where条件中含有至少一个字段添加了索引,才会触发行锁,否则就是表锁
    c.结论3
        除了上面的条件,还有两种特殊情况:
        当表中只有联合索引,而且是根据查询条件所创建的索引」(总之,就是他俩涉及的列一模一样),触发的将是表锁,而不是行锁;
        当查询结果过多(具体超过全表记录的百分之多少),索引将会失效,触发全表扫描,此时,也会触发表锁。
    d.结论4
        行锁并不一定就是只锁了一行,具体视情况而定
    e.结论5
        当where条件中携带表主键时,即使不走显式索引」(主键本身就是一种唯一索引l),触发的是行锁,而不是表锁。
    f.结论6
        当执行插入语句insert into时,同样的,当插入的字段含有索引字段时,触发的是行锁;否则,触发的是表锁。

1.15 [2]数据库锁:InnoDB使用锁

01.InnoDB啥时候用表锁,啥时候用行锁?
    a.分类1
        a.全局锁
            执行全库逻辑备份时,可以用 Flush tables with read lock 执行全局锁
        b.表级锁
            ├── 表锁
            │   ├── 存储引擎不支持行级锁时,使用表锁
            │   └── SQL语句又混用匹配到索引,使用表锁
            ├── 元数据锁
            │   ├── 对表做普通改查时,会加上MDL读锁
            │   └── 对表结构做变更时,会加上MDL写锁
            └── 意向锁
                └── 对表中的行记录加锁时,会用到意向锁
        c.行级锁
            └── 普通改查匹配到索引时,会使用行级锁
    b.分类2
        a.按用途
            悲观锁
            乐观锁
        b.按粒度
            行级锁
            表级锁
        c.按类型
            共享锁
            互斥锁
            意向锁
        d.按实现方式
            记录锁
            间隙锁
            临键锁
            插入意向锁
    c.分类3
        a.主键
            有值  行锁
            空值  间隙锁
        b.唯一索引
            有值  行锁
            空值  间隙锁
        c.普通索引
            有值  间隙锁
            空值  间隙锁
        d.索引 + 范围查询
            间隙锁
        e.无索引
            表锁

1.16 [2]数据库锁:InnoDB加锁原则

00.MySQL的加锁原则
    a.原则1
        加锁的基本单位是 next-key lock,是一个前开后闭区间
    b.原则2
        查找过程中访问到的对象才会加锁
    c.优化1
        索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁
    d.优化2
        索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁
    e.一个bug
        唯一索引上的范围查询会访问到不满足条件的第一个值为止

01.Innodb锁机制
    a.说明
        数据库使用锁是为了支持更好的并发,提供数据的完整性和一致性
        InnoDB是一个支持行锁的存储引擎,锁的类型有:共享锁(S)、排他锁(X)、意向共享(IS)、意向排他(IX)
        为了提供更好的并发,InnoDB提供了非锁定读:不需要等待访问行上的锁释放,读取行的一个快照
        该方法是通过InnoDB的一个特性:MVCC来实现的
    b.InnoDB有三种行锁的算法
        a.Record Lock
            单个行记录上的锁
        b.Gap Lock
            间隙锁,锁定一个范围,但不包括记录本身。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况
        c.Next-Key Lock
            1+2,锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题

02.测试1:默认RR隔离级别
    a.创建表和插入数据
        root@localhost : test 10:56:10>
        create table t(a int,key idx_a(a))engine =innodb;
        Query OK, 0 rows affected (0.20 sec)

        root@localhost : test 10:56:13>
        insert into t values(1),(3),(5),(8),(11);
        Query OK, 5 rows affected (0.00 sec)
        Records: 5  Duplicates: 0  Warnings: 0

        root@localhost : test 10:56:15>
        select * from t;
        +------+
        | a    |
        +------+
        |    1 |
        |    3 |
        |    5 |
        |    8 |
        |   11 |
        +------+
        5 rows in set (0.00 sec)
    b.section A
        root@localhost : test 10:56:27>
        start transaction;
        Query OK, 0 rows affected (0.00 sec)

        root@localhost : test 10:56:29>
        select * from t where a = 8 for update;
        +------+
        | a    |
        +------+
        |    8 |
        +------+
        1 row in set (0.00 sec)
    c.section B
        root@localhost : test 10:54:50>
        begin;
        Query OK, 0 rows affected (0.00 sec)

        root@localhost : test 10:56:51>
        select * from t;
        +------+
        | a    |
        +------+
        |    1 |
        |    3 |
        |    5 |
        |    8 |
        |   11 |
        +------+
        5 rows in set (0.00 sec)

        root@localhost : test 10:56:54>
        insert into t values(2);
        Query OK, 1 row affected (0.00 sec)

        root@localhost : test 10:57:01>
        insert into t values(4);
        Query OK, 1 row affected (0.00 sec)

        ++++++++++

        root@localhost : test 10:57:04>insert into t values(6);
        root@localhost : test 10:57:11>insert into t values(7);
        root@localhost : test 10:57:15>insert into t values(9);
        root@localhost : test 10:57:33>insert into t values(10);

        ++++++++++ 上面全被锁住,阻塞住了

        root@localhost : test 10:57:39>insert into t values(12);
        Query OK, 1 row affected (0.00 sec)
    d.问题分析
        为什么section B上面的插入语句会出现锁等待的情况?
        InnoDB是行锁,在section A里面锁住了a=8的行,其他应该不受影响。why?
        -----------------------------------------------------------------------------------------------------
        因为InnoDB对于行的查询都是采用了Next-Key Lock的算法,锁定的不是单个值,而是一个范围(GAP)
        上面索引值有1,3,5,8,11,其记录的GAP的区间如下:
        是一个左开右闭的空间(原因是默认主键的有序自增的特性,结合后面的例子说明)
        (-∞,1],(1,3],(3,5],(5,8],(8,11],(11,+∞)
        -----------------------------------------------------------------------------------------------------
        特别需要注意的是,InnoDB存储引擎还会对辅助索引下一个键值加上gap lock。如上面分析,那就可以解释了
    e.问题说明
        root@localhost : test 10:56:29>select * from t where a = 8 for update;
        +------+
        | a    |
        +------+
        |    8 |
        +------+
        1 row in set (0.00 sec)
        -----------------------------------------------------------------------------------------------------
        该SQL语句锁定的范围是(5,8],下个下个键值范围是(8,11],所以插入5~11之间的值的时候都会被锁定,要求等待
        即:插入5,6,7,8,9,10 会被锁住。插入非这个范围内的值都正常

03.测试1:默认RR隔离级别,2016-07-21更新
    a.说明
        因为例子里没有主键,所以要用隐藏的ROWID来代替,数据根据Rowid进行排序
        而Rowid是有一定顺序的(自增),所以其中11可以被写入,5不能被写入,不清楚的可以再看一个有主键的例子:
    b.会话1
        01:43:07>
        create table t(id int,name varchar(10),key idx_id(id),primary key(name))engine =innodb;
        Query OK, 0 rows affected (0.02 sec)

        01:43:11>
        insert into t values(1,'a'),(3,'c'),(5,'e'),(8,'g'),(11,'j');
        Query OK, 5 rows affected (0.01 sec)
        Records: 5  Duplicates: 0  Warnings: 0

        01:44:03>
        select @@global.tx_isolation, @@tx_isolation;
        +-----------------------+-----------------+
        | @@global.tx_isolation | @@tx_isolation  |
        +-----------------------+-----------------+
        | REPEATABLE-READ       | REPEATABLE-READ |
        +-----------------------+-----------------+
        1 row in set (0.01 sec)

        01:44:58>select * from t;
        +------+------+
        | id   | name |
        +------+------+
        |    1 | a    |
        |    3 | c    |
        |    5 | e    |
        |    8 | g    |
        |   11 | j    |
        +------+------+
        5 rows in set (0.00 sec)

        01:45:07>
        start transaction;

        01:45:09>
        delete from t where id=8;
        Query OK, 1 row affected (0.01 sec)
    c.会话2
        ---
        01:50:38>
        select @@global.tx_isolation, @@tx_isolation;
        +-----------------------+-----------------+
        | @@global.tx_isolation | @@tx_isolation  |
        +-----------------------+-----------------+
        | REPEATABLE-READ       | REPEATABLE-READ |
        +-----------------------+-----------------+
        1 row in set (0.01 sec)

        01:50:48>
        start transaction;

        01:50:51>
        select * from t;
        +------+------+
        | id   | name |
        +------+------+
        |    1 | a    |
        |    3 | c    |
        |    5 | e    |
        |    8 | g    |
        |   11 | j    |
        +------+------+
        5 rows in set (0.01 sec)

        01:51:35>
        insert into t(id,name) values(6,'f');
        ^CCtrl-C -- sending "KILL QUERY 9851" to server ...
        Ctrl-C -- query aborted.
        ERROR 1317 (70100): Query execution was interrupted

        01:53:32>
        insert into t(id,name) values(5,'e1');
        ^CCtrl-C -- sending "KILL QUERY 9851" to server ...
        Ctrl-C -- query aborted.
        ERROR 1317 (70100): Query execution was interrupted

        01:53:41>
        insert into t(id,name) values(7,'h');
        ^CCtrl-C -- sending "KILL QUERY 9851" to server ...
        Ctrl-C -- query aborted.
        ERROR 1317 (70100): Query execution was interrupted

        01:54:43>
        insert into t(id,name) values(8,'gg');
        ^CCtrl-C -- sending "KILL QUERY 9851" to server ...
        Ctrl-C -- query aborted.
        ERROR 1317 (70100): Query execution was interrupted

        01:55:10>
        insert into t(id,name) values(9,'k');
        ^CCtrl-C -- sending "KILL QUERY 9851" to server ...
        Ctrl-C -- query aborted.
        ERROR 1317 (70100): Query execution was interrupted

        01:55:23>
        insert into t(id,name) values(10,'p');
        ^CCtrl-C -- sending "KILL QUERY 9851" to server ...
        Ctrl-C -- query aborted.
        ERROR 1317 (70100): Query execution was interrupted

        01:55:33>
        insert into t(id,name) values(11,'iz');
        ^CCtrl-C -- sending "KILL QUERY 9851" to server ...
        Ctrl-C -- query aborted.
        ERROR 1317 (70100): Query execution was interrupted

        #########上面看到 id:5,6,7,8,9,10,11都被锁了。

        #########下面看到 id:5,11 还是可以插入的
        01:54:33>
        insert into t(id,name) values(5,'cz');
        Query OK, 1 row affected (0.01 sec)

        01:55:59>
        insert into t(id,name) values(11,'ja');
        Query OK, 1 row affected (0.01 sec)
    d.说明
        因为会话1已经对id=8的记录加了一个X锁
        由于是RR隔离级别,INNODB要防止幻读需要加GAP锁:即id=5(8的左边),id=11(8的右边)之间需要加间隙锁(GAP)
        这样[5,e]和[8,g],[8,g]和[11,j]之间的数据都要被锁
        上面测试已经验证了这一点,根据索引的有序性,数据按照主键(name)排序
        后面写入的[5,cz]([5,e]的左边)和[11,ja]([11,j]的右边)不属于上面的范围从而可以写入

04.测试1:插入超时失败后,会怎么样?
    a.说明
        超时时间的参数:innodb_lock_wait_timeout ,默认是50秒
        超时是否回滚参数:innodb_rollback_on_timeout 默认是OFF
    b.会话1
        root@localhost : test 04:48:51>
        start transaction;
        Query OK, 0 rows affected (0.00 sec)

        root@localhost : test 04:48:53>
        select * from t where a = 8 for update;
        +------+
        | a    |
        +------+
        |    8 |
        +------+
        1 row in set (0.01 sec)
    c.会话2
        root@localhost : test 04:49:04>
        start transaction;
        Query OK, 0 rows affected (0.00 sec)

        root@localhost : test 04:49:07>
        insert into t values(12);
        Query OK, 1 row affected (0.00 sec)

        root@localhost : test 04:49:13>
        insert into t values(10);
        ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
        root@localhost : test 04:50:06>
        select * from t;
        +------+
        | a    |
        +------+
        |    1 |
        |    3 |
        |    5 |
        |    8 |
        |   11 |
        |   12 |
        +------+
        6 rows in set (0.00 sec)
    d.说明
        经过测试,不会回滚超时引发的异常
        当参数innodb_rollback_on_timeout 设置成ON时,则可以回滚,会把插进去的12回滚掉
        默认情况下,InnoDB存储引擎不会回滚超时引发的异常,除死锁外

05.测试2:Record Lock
    a.创建表和插入数据
        root@localhost : test 04:58:49>create table t(a int primary key)engine =innodb;
        Query OK, 0 rows affected (0.19 sec)

        root@localhost : test 04:59:02>insert into t values(1),(3),(5),(8),(11);
        Query OK, 5 rows affected (0.00 sec)
        Records: 5  Duplicates: 0  Warnings: 0

        root@localhost : test 04:59:10>select * from t;
        +----+
        | a  |
        +----+
        |  1 |
        |  3 |
        |  5 |
        |  8 |
        | 11 |
        +----+
        5 rows in set (0.00 sec)
    b.会话1
        root@localhost : test 04:59:30>start transaction;
        Query OK, 0 rows affected (0.00 sec)

        root@localhost : test 04:59:33>select * from t where a = 8 for update;
        +---+
        | a |
        +---+
        | 8 |
        +---+
        1 row in set (0.00 sec)
    c.会话2
        root@localhost : test 04:58:41>start transaction;
        Query OK, 0 rows affected (0.00 sec)

        root@localhost : test 04:59:45>insert into t values(6);
        Query OK, 1 row affected (0.00 sec)

        root@localhost : test 05:00:05>insert into t values(7);
        Query OK, 1 row affected (0.00 sec)

        root@localhost : test 05:00:08>insert into t values(9);
        Query OK, 1 row affected (0.00 sec)

        root@localhost : test 05:00:10>insert into t values(10);
        Query OK, 1 row affected (0.00 sec)
    d.问题分析
        为什么section B上面的插入语句可以正常,和测试一不一样?
        因为InnoDB对于行的查询都是采用了Next-Key Lock的算法,
        锁定的不是单个值,而是一个范围,按照这个方法是会和第一次测试结果一样
        但是,当查询的索引含有唯一属性的时候,Next-Key Lock 会进行优化
        将其降级为Record Lock,即仅锁住索引本身,不是范围。
    e.注意
        a.说明
            通过主键或则唯一索引来锁定不存在的值,也会产生GAP锁定
        b.会话1
            04:22:38>show create table t\G
            *************************** 1. row ***************************
                   Table: t
            Create Table: CREATE TABLE `t` (
              `id` int(11) NOT NULL,
              `name` varchar(10) DEFAULT NULL,
              PRIMARY KEY (`id`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
            1 row in set (0.00 sec)

            04:22:49>start transaction;

            04:23:16>select * from t where id = 15 for update;
            Empty set (0.00 sec)
        c.会话2
            04:26:10>insert into t(id,name) values(10,'k');
            Query OK, 1 row affected (0.01 sec)

            04:26:26>insert into t(id,name) values(12,'k');
            ^CCtrl-C -- sending "KILL QUERY 9851" to server ...
            Ctrl-C -- query aborted.
            ERROR 1317 (70100): Query execution was interrupted
            04:29:32>insert into t(id,name) values(16,'kxx');
            ^CCtrl-C -- sending "KILL QUERY 9851" to server ...
            Ctrl-C -- query aborted.
            ERROR 1317 (70100): Query execution was interrupted
            04:29:39>insert into t(id,name) values(160,'kxx');
            ^CCtrl-C -- sending "KILL QUERY 9851" to server ...
            Ctrl-C -- query aborted.
            ERROR 1317 (70100): Query execution was interrupted
    f.如何让测试一不阻塞?可以显式的关闭Gap Lock
        a.修改事务隔离级别
            // 把事务隔离级别改成Read Committed,提交读、不可重复读
            SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
        b.修改参数
            innodb_locks_unsafe_for_binlog 设置为1

1.17 [2]数据库锁:先删除后插入的并发问题

00.汇总
    问题           RC表现             RR表现             解决方案
    重复删除数据    无影响             无影响             无需处理
    重复插入数据    唯一键冲突异常      唯一键冲突异常     强制串行化+异常捕获
    间隙锁导致死锁  不发生             可能会发生         切换为RC级别

01.先删除后插入的并发问题
    a.案例背景
        a.说明
            比如两个用户同时批量下发审批操作,审批的是同一批数据,并发调用用一个方法
            方法中存在先删除后插入的操作,高并发情况下,可能会引发数据并发问题和死锁问题。
        b.代码
            public void submitPayOrder(String orderPkId, String operator) {
                TenantOrderPay order = tenantOrderPayMapper.selectById(orderPkId);
                BizAssert.notNull(order, "订单为空");

                tenantOrderAuditMapper.delete(new LambdaQueryWrapper<TenantOrderAudit>().eq(TenantOrderAudit::getOrderPkId, orderPkId));
                tenantOrderAuditMapper.insert(new TenantOrderAudit()
                        .setAuditPkId(IdWorker.getIdStr())
                        .setProjectPkId(order.getProjectPkId())
                        .setAuditCode(bizCodeGenerator.getCode(ECommonCode.ORDER_AUDIT))
                        .setOrderPkId(orderPkId).setOrderType(EOrderType.PAY.getType())
                        .setCreateTime(new Date())
                        .setUpdateTime(new Date())
                        .setAuditRecord(new TenantOrderAuditRecord().submit(operator).toJson())
                        .setAuditStatus(EOrderAuditStatus.AUDIT.getType()));
            }
    b.并发引发的问题
        a.数据库隔离级别
            a.读已提交(RC)
                可见性规则:事务只能看到其他事务已提交的数据。
                锁机制:
                    DELETE 操作对符合条件的行加 排他锁(X锁),其他事务需等待锁释放。
                    唯一性检查基于已提交数据,允许幻读(Phantom Read)。
            b.可重复读(RR)
                可见性规则:事务内多次读取同一范围数据的结果一致。
                锁机制:
                    引入 间隙锁(Gap Lock),锁定范围内的间隙,防止其他事务插入新数据。
                    唯一性检查基于当前事务的快照,可能因间隙锁导致死锁。
        b.数据库默认隔离级别
            a.重复删除数据
                根本原因:在RC/RR隔离级别下,DELETE 操作会对符合条件的行加 排他锁(X锁),其他事务在删除操作未提交时会阻塞等待锁释放。
                产生影响:对系统并无影响,多个事务中仅一个能成功删除数据,后续事务的DELETE操作实际影响0行。
                发生概率:0(无需额外处理)
            b.重复插入数据
                根本原因:在RC/RR下,若两个事务同时插入相同唯一键值,第一个是事务插入成功,第二个事务会因唯一约束插入失败。
                产生影响:直接报错,系统抛出错误异常。
                发生概率:高,触发条件:并发插入主键冲突。
            c.间隙锁产生死锁
                根本原因:间隙锁在可重复读(RR)级别下默认生效,当使用主键查询,数据不存在则会产生间隙锁,并发删除和写入会有死锁的概率发生。
                产生影响:导致死锁,CPU飙高,资源得不到释放。
                发生的概率:中,触发条件:主键等值查询且数据不存在时,RR级别加间隙锁。

02.解决方案
    a.通用操作
        1.强制串行化:使用 select for update 显式锁定主订单行,确保后续操作的原子性。
        2.事务边界控制:添加 @Transactional 注解,确保锁持有到事务结束。
        3.异常处理:捕获唯一键冲突异常,记录日志并回滚事务。
        备注:当数据库执行select for update时会获取被select中的数据行的行锁,因此其他并发执行的select for update如果试图选中同一行则会发生排斥(需要等待行锁被释放),因此达到锁的效果。 select for update获取的行锁会在当前事务结束时自动释放。
    b.隔离级别选择
        推荐使用RC级别,避免间隙锁导致的死锁问题。
        调整数据库隔离级别:SET GLOBAL transaction_isolation = 'READ-COMMITTED';
    c.分布式锁
        适合集群环境下跨JVM串行运行。
        引入会带来一定的性能损耗。

03.代码优化
    a.正常情况
        a.说明
            通过主键查询数据的时候,使用select for update加行锁,确保后续删除和插入操作的原子性,避免并发冲突,确保同一时间只有一个事务处理该订单。
            对于删除、插入等多个DML操作,加上@Transactional注解,确保方法在事务中执行,这样锁会保持到事务结束。
            处理可能的唯一约束异常,使用try catch 进行捕获,打印相关日志,并抛出异常,否则事务无法回滚。
            使用Redisson分布式锁,可确保集群环境下,JVM串行化,但也会带来一定的性能损耗(数据库锁和分布式锁二选其一)。
        b.代码
            @Override
            @Transactional(rollbackFor = Exception.class)
            public void submitPayOrder(String orderPkId, String operator) {
                lockUtil.exec(ESysLockKeyType.SUBMIT_PAY_ORDER.getKey().apply(new Object[]{orderPkId}), ()->{
                    // 1. 通过 SELECT FOR UPDATE 锁定主订单行,直到事务结束
                    TenantOrderPay order = tenantOrderPayMapper.selectOne(
                        new LambdaQueryWrapper<TenantOrderPay>()
                        .eq(TenantOrderPay::getOrderPkId, orderPkId)
                        .last("FOR UPDATE")
                    );
                    BizAssert.notNull(order, "订单为空");
                    // 2. 删除审计记录
                    tenantOrderAuditMapper.delete(new LambdaQueryWrapper<TenantOrderAudit>().eq(TenantOrderAudit::getOrderPkId, orderPkId));
                    // 3. 生成并插入新审计记录
                    TenantOrderAudit tenantOrderAudit = new TenantOrderAudit()
                    .setAuditPkId(IdWorker.getIdStr())
                    .setProjectPkId(order.getProjectPkId())
                    .setAuditCode(bizCodeGenerator.getCode(ECommonCode.ORDER_AUDIT))
                    .setOrderPkId(orderPkId).setOrderType(EOrderType.PAY.getType())
                    .setCreateTime(new Date())
                    .setUpdateTime(new Date())
                    .setAuditRecord(new TenantOrderAuditRecord().submit(operator).toJson())
                    .setAuditStatus(EOrderAuditStatus.AUDIT.getType());

                    // 4.插入订单审核记录
                    try {
                        tenantOrderAuditMapper.insert(tenantOrderAudit);
                    } catch (Exception e) {
                        log.error("订单审核记录插入失败", e);
                        throw new BusinessException("审批记录冲突");
                    }
                }, ("提交审批订单失败"));
            }
    b.数据回滚
        a.说明
            在 读已提交(RC) 隔离级别下,事务 B 的回滚 会不会恢复已被事务 A 删除的数据。答案:不会。
        b.场景复现与关键点
            假设事务 A 和事务 B 并发执行以下操作:
            -------------------------------------------------------------------------------------------------
            事务 A:
            DELETE 删除数据(加 X 锁)。
            INSERT 插入新记录(生成唯一主键)。
            提交。
            -------------------------------------------------------------------------------------------------
            事务 B:
            DELETE 尝试删除同一批数据(等待事务 A 的 X 锁释放)。
            获取锁后,发现数据已被事务 A 删除,实际 DELETE 操作影响 0 行。
            INSERT 时因唯一键冲突失败,触发回滚。
    c.关键机制解析
        a.锁的获取与等待:
            事务 A 的 DELETE 会锁定符合条件的行(X 锁直到事务结束)。
            事务 B 的 DELETE 必须等待事务 A 提交或回滚后才能继续。
        b.读已提交(RC)的可见性:
            事务 B 在获取锁后,只能看到事务 A 已提交的删除结果。
            若事务 A 已提交,事务 B 的 DELETE 操作实际 不会删除任何数据(因数据已被事务 A 删除)。
        c.事务 B 回滚的影响:
            由于事务 B 的 DELETE 操作实际未删除数据(影响 0 行),回滚时 无数据可恢复。
            事务 A 的删除和插入操作已提交,数据状态正常。
    d.删除场景
        在RC级别下,事务A删除数据后,事务B看不到事务A未提交的删除,但是一旦事务A提交事务,事务B查询不到已经被事务A删除的数据。
        在RR级别下,事务B在事务A提交后可能仍然看到旧数据,因为可重复读会保持一致性视图。
        再次分析代码:如果数据被事务A删除成功,事务B不应该查出数据,直接抛出异常:订单为空。

1.18 [3]分布式锁

01.分布式锁
    a.定义
        分布式锁是在【分布式环境】下【保证某个资源或操作的独占访问的一种机制】
    b.实现方式
        基于数据库:利用数据库的行锁或表锁,通过更新记录状态实现锁。
        基于 Redis:使用 Redis 的 SETNX 命令(原子性)来创建锁,并设置过期时间以防止死锁。 / Redisson分布式锁
        基于 Zookeeper:使用 Zookeeper 的顺序节点和临时节点机制实现分布式锁。
    c.关键特性
        互斥性:同一时刻只有一个客户端可以获得锁。
        可重入性(可选):锁的拥有者可以多次获得锁。
        超时机制:防止因故障导致锁一直无法释放,可以设置锁的过期时间。
        公平性(可选):按照请求顺序来获取锁。

1.19 [3]分布式锁:3种

01.redisson分布式锁
    Redisson 是一个基于 Redis 的 Java 客户端,它简化了 Redis 的使用并提供了分布式锁的功能

02.setnx(存在死锁)+Lua脚本(lua原子性+事务)
    a.确保原子性
        通过 Lua 脚本,可以在一次请求中完成 SETNX 和 EXPIRE 操作,确保在 Redis 内部操作的原子性
        即便客户端在锁定过程中出现故障,Redis 也会确保这个脚本执行的原子性
    b.防止死锁
        Lua 脚本设置了锁的过期时间,因此,即便客户端崩溃或超时,锁也会自动释放,避免死锁
    c.简化逻辑
        将锁的创建和过期时间设置合并到一个操作中,不需要客户端多次调用 Redis

03.zookeeper分布式锁(临时节点和序列号)
    使用 ZooKeeper 提供的分布式锁功能,可以通过【临时节点和序列号】来实现锁机制
    创建一个顺序节点,每次获得锁时,检查最小的序列号

1.20 [4]Redis锁:坑点

00.汇总
    1.非原子操作
    2.忘了释放锁
    3.释放了别人的锁
    4.大量失败请求
    5.锁重入问题
    6.锁竞争问题
    7.锁超时问题

01.非原子操作
    a.代码
        if (jedis.setnx(lockKey, val) == 1) {
           jedis.expire(lockKey, timeout);
        }
    b.说明
        这段代码确实可以加锁成功,但你有没有发现什么问题?
        加锁操作和后面的设置超时时间是分开的,并非原子操作
        假如加锁成功,但是设置超时时间失败了,该lockKey就变成永不失效
        假如在高并发场景中,有大量的lockKey加锁成功了,但不会失效,有可能直接导致redis内存空间不足
    c.总结
        setNx命令加锁操作和设置超时时间是分开的,并非原子操作

02.忘了释放锁
    a.代码
        String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
        if ("OK".equals(result)) {
            return true;
        }
        return false;
    b.说明
        lockKey:锁的标识
        requestId:请求id
        NX:只在键不存在时,才对键进行设置操作
        PX:设置键的过期时间为 millisecond 毫秒
        expireTime:过期时间
    c.总结
        set命令是原子操作,加锁和设置超时时间,一个命令就能轻松搞定
    d.说明
        使用set命令加锁,表面上看起来没有问题
        但如果仔细想想,加锁之后,每次都要达到了超时时间才释放锁,会不会有点不合理?
        加锁后,如果不及时释放锁,会有很多问题
    e.分布式锁
        1.手动加锁
        2.业务操作
        3.手动释放锁
        4.如果手动释放锁失败了,则达到超时时间,redis会自动释放锁
    f.如何释放锁呢?
        try{
          String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
          if ("OK".equals(result)) {
              return true;
          }
          return false;
        } finally {
            unlock(lockKey);
        }
    g.说明
        需要捕获业务代码的异常,然后在finally中释放锁。换句话说就是:无论代码执行成功或失败了,都需要释放锁
        此时,有些朋友可能会问:假如刚好在释放锁的时候,系统被重启了,或者网络断线了,或者机房断点了,不也会导致释放锁失败
        这是一个好问题,因为这种小概率问题确实存在
        但还记得前面我们给锁设置过超时时间吗?即使出现异常情况造成释放锁失败,但到了我们设定的超时时间,锁还是会被redis自动释放
        但只在finally中释放锁,就够了吗

03.释放了别人的锁
    a.说明
        做人要厚道,先回答上面的问题:只在finally中释放锁,当然是不够的,因为释放锁的姿势,还是不对
        哪里不对?
        答:在多线程场景中,可能会出现释放了别人的锁的情况
    b.说明
        有些朋友可能会反驳:假设在多线程场景中,线程A获取到了锁,如果线程A没有释放锁,线程B是获取不到锁的,何来释放了别人锁之说?
        答:假如线程A和线程B,都使用lockKey加锁。线程A加锁成功了,但是由于业务功能耗时时间很长,超过了设置的超时时间
        这时候,redis会自动释放lockKey锁。此时,线程B就能给lockKey加锁成功了,接下来执行它的业务操作
        恰好这个时候,线程A执行完了业务功能,释放了锁lockKey。这不就出问题了,线程B的锁,被线程A释放了
        我想这个时候,线程B肯定哭晕在厕所里,并且嘴里还振振有词。
    c.说明
        那么,如何解决这个问题呢?
        不知道你们注意到没?在使用set命令加锁时,除了使用lockKey锁标识,还多设置了一个参数:requestId,为什么要需要记录requestId呢?
        答:requestId是在释放锁的时候用的。
    d.代码
        if (jedis.get(lockKey).equals(requestId)) {
            jedis.del(lockKey);
            return true;
        }
        return false;
    e.说明
        在释放锁的时候,先获取到该锁的值(之前设置值就是requestId),然后判断跟之前设置的值是否相同,如果相同才允许删除锁,返回成功。如果不同,则直接返回失败。
        换句话说就是:自己只能释放自己加的锁,不允许释放别人加的锁。
        这里为什么要用requestId,用userId不行吗?
        答:如果用userId的话,对于请求来说并不唯一,多个不同的请求,可能使用同一个userId。而requestId是全局唯一的,不存在加锁和释放锁乱掉的情况。
        此外,使用lua脚本,也能解决释放了别人的锁的问题:
    f.代码
        if redis.call('get', KEYS[1]) == ARGV[1] then
         return redis.call('del', KEYS[1])
        else
          return 0
        end
    g.说明
        lua脚本能保证查询锁是否存在和删除锁是原子操作,用它来释放锁效果更好一些
        说到lua脚本,其实加锁操作也建议使用lua脚本:
        if (redis.call('exists', KEYS[1]) == 0) then
            redis.call('hset', KEYS[1], ARGV[2], 1);
            redis.call('pexpire', KEYS[1], ARGV[1]);
         return nil;
        end
        if (redis.call('hexists', KEYS[1], ARGV[2]) == 1)
           redis.call('hincrby', KEYS[1], ARGV[2], 1);
           redis.call('pexpire', KEYS[1], ARGV[1]);
          return nil;
        end;
        return redis.call('pttl', KEYS[1]);

04.大量失败请求
    a.说明
        上面的加锁方法看起来好像没有问题,但如果你仔细想想
        如果有1万的请求同时去竞争那把锁,可能只有一个请求是成功的,其余的9999个请求都会失败
        在秒杀场景下,会有什么问题?
        答:每1万个请求,有1个成功。再1万个请求,有1个成功。如此下去,直到库存不足
        这就变成均匀分布的秒杀了,跟我们想象中的不一样
    b.说明
        如何解决这个问题呢?
        此外,还有一种场景:
        比如,有两个线程同时上传文件到sftp,上传文件前先要创建目录。假设两个线程需要创建的目录名都是当天的日期
        比如:20210920,如果不做如何控制,这样直接并发的创建,第二个线程会失败
        有同学会说:这还不容易,加一个redis分布式锁就能解决问题了,此外再判断一下,如果目录已经存在就不创建,只有目录不存在才需要创建
    c.代码
        try {
          String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
          if ("OK".equals(result)) {
            if(!exists(path)) {
               mkdir(path);
            }
            return true;
          }
        } finally{
            unlock(lockKey,requestId);
        }
        return false;
    d.说明
        答:只是加redis分布式锁是不够的,因为第二个请求如果加锁失败了,接下来,是返回失败呢?还是返回成功呢?
        显然肯定是不能返回失败的,如果返回失败了,这个问题还是没有被解决
        如果文件还没有上传成功,直接返回成功会有更大的问题。头疼,到底该如何解决呢?
        答:使用自旋锁。
    e.代码
        try {
          Long start = System.currentTimeMillis();
          while(true) {
             String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
             if ("OK".equals(result)) {
                if(!exists(path)) {
                   mkdir(path);
                }
                return true;
             }

             long time = System.currentTimeMillis() - start;
              if (time>=timeout) {
                  return false;
              }
              try {
                  Thread.sleep(50);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
        } finally{
            unlock(lockKey,requestId);
        }
        return false;
    f.说明
        在规定的时间,比如500毫秒内,自旋不断尝试加锁(说白了,就是在死循环中,不断尝试加锁)
        如果成功则直接返回。如果失败,则休眠50毫秒,再发起新一轮的尝试
        如果到了超时时间,还未加锁成功,则直接返回失败

05.锁重入问题
    a.说明
        我们都知道redis分布式锁是互斥的
        如果我们对某个key加锁了,如果该key对应的锁还没失效,再用相同key去加锁,大概率会失败
        没错,大部分场景是没问题的。
    b.说明
        为什么说是大部分场景呢?
        因为还有这样的场景:
        假设在某个请求中,需要获取一颗满足条件的菜单树或者分类树。我们以菜单为例,这就需要在接口中从根节点开始,递归遍历出所有满足条件的子节点,然后组装成一颗菜单树
        需要注意的是菜单不是一成不变的,在后台系统中运营同学可以动态添加、修改和删除菜单。为了保证在并发的情况下,每次都可能获取最新的数据,这里可以加redis分布式锁
        加redis分布式锁的思路是对的。但接下来问题来了,在递归方法中递归遍历多次,每次都是加的同一把锁。递归第一层当然是可以加锁成功的,但递归第二层、第三层...第N层,不就会加锁失败了
    c.递归方法中加锁的伪代码
        private int expireTime = 1000;
        public void fun(int level,String lockKey,String requestId){
          try{
             String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
             if ("OK".equals(result)) {
                if(level<=10){
                   this.fun(++level,lockKey,requestId);
                } else {
                   return;
                }
             }
             return;
          } finally {
             unlock(lockKey,requestId);
          }
        }
    d.说明
        如果你直接这么用,看起来好像没有问题。但最终执行程序之后发现,等待你的结果只有一个:出现异常
        因为从根节点开始,第一层递归加锁成功,还没释放说,就直接进入第二层递归
        因为requestId作为key的锁已经存在,所以第二层递归大概率会加锁失败,然后返回到第一层
        第一层接下来正常释放锁,然后整个递归方法直接返回了
    e.说明
        这下子,大家知道出现什么问题了吧?
        没错,递归方法其实只执行了第一层递归就返回了,其他层递归由于加锁失败,根本没法执行
        那么这个问题该如何解决呢?
        答:使用可重入锁
    f.以redisson框架为例,它的内部实现了可重入锁的功能
        private int expireTime = 1000;
        public void run(String lockKey) {
          RLock lock = redisson.getLock(lockKey);
          this.fun(lock,1);
        }
        public void fun(RLock lock,int level){
          try{
              lock.lock(5, TimeUnit.SECONDS);
              if(level<=10){
                 this.fun(lock,++level);
              } else {
                 return;
              }
          } finally {
             lock.unlock();
          }
        }
    g.redisson加锁主要是通过以下脚本实现的
        if (redis.call('exists', KEYS[1]) == 0)
        then
           redis.call('hset', KEYS[1], ARGV[2], 1);        redis.call('pexpire', KEYS[1], ARGV[1]);
           return nil;
        end;
        if (redis.call('hexists', KEYS[1], ARGV[2]) == 1)
        then
          redis.call('hincrby', KEYS[1], ARGV[2], 1);
          redis.call('pexpire', KEYS[1], ARGV[1]);
          return nil;
        end;
        return redis.call('pttl', KEYS[1]);
        -----------------------------------------------------------------------------------------------------
        其中:
        KEYS[1]: 锁名
        ARGV[1]: 过期时间
        ARGV[2]: uuid + ":" + threadId,可认为是requestId
        -----------------------------------------------------------------------------------------------------
        先判断如果锁名不存在,则加锁
        然后判断判断如果锁名和requestId值都存在,则使用hincrby命令给该锁名和requestId值计数,每次都加1。注意一下,这里就是重入锁的关键,锁重入一次就加1
        如果锁名存在,但值不是requestId,则返回过期时间
    h.redisson释放锁主要是通过以下脚本实现的
        if (redis.call('hexists', KEYS[1], ARGV[3]) == 0)
        then
          return nil
        end
        local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1)
        if (counter > 0)
        then
            redis.call('pexpire', KEYS[1], ARGV[2]);       return 0;
         else
           redis.call('del', KEYS[1]);
           redis.call('publish', KEYS[2], ARGV[1]);
           return 1;
        end;
        return nil
        -----------------------------------------------------------------------------------------------------
        先判断如果锁名和requestId值不存在,则时间返回
        如果锁名和requestId值存在,则重入锁减1
        如果减1后,重入锁的value值还大于0,说明还有引用,则重试设置过期时间
        如果减1后,重入锁的value值还等于0,则可以删除锁,然后发消息通知等待线程抢锁
        再次强调一下,如果你们系统可以容忍数据暂时不一致,不加锁也行,我在这里只是举个例子,本节内容并不适用于所有场景

06.锁竞争问题
    a.说明
        如果有大量写入的场景,使用普通的redis分布式锁是没有问题的
        但如果有些业务场景,写入的操作比较少,反而有大量读取的操作。直接使用普通的redis分布式锁,性能会不会不太好?
        我们都知道,锁的粒度越粗,多个线程抢锁时竞争就越激烈,造成多个线程锁等待的时间也就越长,性能也就越差
        所以,提升redis分布式锁性能的第一步,就是要把锁的粒度变细
    b.读写锁
        a.说明
            众所周知,加锁的目的是为了保证,在并发环境中读写数据的安全性,即不会出现数据错误或者不一致的情况。
            但在绝大多数实际业务场景中,一般是读数据的频率远远大于写数据。而线程间的并发读操作是并不涉及并发安全问题,我们没有必要给读操作加互斥锁,只要保证读写、写写并发操作上锁是互斥的就行,这样可以提升系统的性能。
            我们以redisson框架为例,它内部已经实现了读写锁的功能。
        b.读锁的伪代码如下
            RReadWriteLock readWriteLock = redisson.getReadWriteLock("readWriteLock");
            RLock rLock = readWriteLock.readLock();
            try {
                rLock.lock();
                //业务操作
            } catch (Exception e) {
                log.error(e);
            } finally {
                rLock.unlock();
            }
        c.写锁的伪代码如下
            RReadWriteLock readWriteLock = redisson.getReadWriteLock("readWriteLock");
            RLock rLock = readWriteLock.writeLock();
            try {
                rLock.lock();
                //业务操作
            } catch (InterruptedException e) {
               log.error(e);
            } finally {
                rLock.unlock();
            }
        d.总结
            将读锁和写锁分开,最大的好处是提升读操作的性能,因为读和读之间是共享的,不存在互斥性。而我们的实际业务场景中,绝大多数数据操作都是读操作。所以,如果提升了读操作的性能,也就会提升整个锁的性能。
            下面总结一个读写锁的特点:
            读与读是共享的,不互斥
            读与写互斥
            写与写互斥
    b.锁分段
        a.说明
            此外,为了减小锁的粒度,比较常见的做法是将大锁:分段
            在java中ConcurrentHashMap,就是将数据分为16段,每一段都有单独的锁,并且处于不同锁段的数据互不干扰,以此来提升锁的性能
        b.放在实际业务场景中,我们可以这样做
            比如在秒杀扣库存的场景中,现在的库存中有2000个商品,用户可以秒杀。为了防止出现超卖的情况,通常情况下,可以对库存加锁。如果有1W的用户竞争同一把锁,显然系统吞吐量会非常低
            为了提升系统性能,我们可以将库存分段,比如:分为100段,这样每段就有20个商品可以参与秒杀
            在秒杀的过程中,先把用户id获取hash值,然后除以100取模。模为1的用户访问第1段库存,模为2的用户访问第2段库存,模为3的用户访问第3段库存,后面以此类推,到最后模为100的用户访问第100段库存
            如此一来,在多线程环境中,可以大大的减少锁的冲突。以前多个线程只能同时竞争1把锁,尤其在秒杀的场景中,竞争太激烈了,简直可以用惨绝人寰来形容,其后果是导致绝大数线程在锁等待。现在多个线程同时竞争100把锁,等待的线程变少了,从而系统吞吐量也就提升了
            需要注意的地方是:将锁分段虽说可以提升系统的性能,但它也会让系统的复杂度提升不少。因为它需要引入额外的路由算法,跨段统计等功能。我们在实际业务场景中,需要综合考虑,不是说一定要将锁分段

07.锁超时问题
    a.说明
        前面提到过,如果线程A加锁成功了,但是由于业务功能耗时时间很长,超过了设置的超时时间,这时候redis会自动释放线程A加的锁。
        有些朋友可能会说:到了超时时间,锁被释放了就释放了呗,对功能又没啥影响。
        答:错,错,错。对功能其实有影响。
    b.说明
        通常我们加锁的目的是:为了防止访问临界资源时,出现数据异常的情况。比如:线程A在修改数据C的值,线程B也在修改数据C的值,如果不做控制,在并发情况下,数据C的值会出问题。
        为了保证某个方法,或者段代码的互斥性,即如果线程A执行了某段代码,是不允许其他线程在某一时刻同时执行的,我们可以用synchronized关键字加锁。
        但这种锁有很大的局限性,只能保证单个节点的互斥性。如果需要在多个节点中保持互斥性,就需要用redis分布式锁。
        做了这么多铺垫,现在回到正题。
    c.说明
        假设线程A加redis分布式锁的代码,包含代码1和代码2两段代码。
        由于该线程要执行的业务操作非常耗时,程序在执行完代码1的时,已经到了设置的超时时间,redis自动释放了锁。而代码2还没来得及执行。
        此时,代码2相当于裸奔的状态,无法保证互斥性。假如它里面访问了临界资源,并且其他线程也访问了该资源,可能就会出现数据异常的情况。(PS:我说的访问临界资源,不单单指读取,还包含写入)
        那么,如何解决这个问题呢?
        答:如果达到了超时时间,但业务代码还没执行完,需要给锁自动续期。

1.21 [4]Redis锁:setnx实现

01.回答
    Redis的setnx命令,【一般使用String结构】

02.操作
    a.加锁命令:SETNX key value
        当键不存在时,对键进行设置操作并返回成功,否则返回失败。KEY 是锁的唯一标识,一般按业务来决定命名
    b.解锁命令:DEL key
        通过删除键值对释放锁,以便其他线程可以通过 SETNX 命令来获取锁
    c.锁超时:EXPIRE key timeout
        设置 key 的超时时间,以保证即使锁没有被显式释放,锁也可以在一定时间后自动释放,避免资源被永远锁住

1.22 [4]Redis锁:setnx缺点

01.局限性
    a.死锁问题
        SETNX 如未设置过期时间,锁忘记删了或加锁线程宕机都会导致死锁,也就是分布式锁一直被占用的情况
    b.锁误删问题
        SETNX 设置了超时时间,但因为执行时间太长,所以在超时时间之内锁已经被自动释放了
        但线程不知道,因此在线程执行结束之后,会把其他线程的锁误删的问题
    c.不可重入问题
        也就是说同一线程在已经获取了某个锁的情况下,如果再次请求获取该锁,则请求会失败(因为只有在第一次能加锁成功)
        也就是说,一个线程不能对自己已持有的锁进行重复锁定
    d.无法自动续期
        线程在持有锁期间,任务未能执行完成,锁可能会因为超时而自动释放。SETNX 无法自动根据任务的执行情况
        设置新的超时实现,以延长锁的时间
    e.Redis单点故障
        如果Redis实例宕机,会导致所有锁失效。虽然可以通过Redis集群和哨兵机制提高可用性,但增加了实现和维护的复杂度

02.尽管如此,很多公司仍然使用Redis实现分布式锁
    a.简单易用
        Redis提供的分布式锁实现相对简单,通过SETNX和EXPIRE命令可以快速上手,开发和集成成本低
    b.高性能
        Redis作为内存数据库,具有高吞吐量和低延迟的特点,可以快速获取和释放锁,满足大部分业务需求
    c.多功能集成
        Redis不仅用于分布式锁,还广泛用于缓存和会话存储,很多系统已经在使用Redis,因此在现有基础上实现分布式锁无需额外基础设施
    d.社区和生态
        Redis有丰富的客户端库和广泛的社区支持,遇到问题可以快速找到解决方案

1.23 [4]Redis锁:setnx+lua脚本

01.回答
    setnx(存在死锁)+Lua脚本(lua原子性+事务)

02.两个原因
    a.setnx:存在死锁
        使用 SETNX 单独设置锁会遇到【死锁】问题(客户端宕机时锁不会释放)
        并且【无法保证原子性】(设置锁后设置超时时间的两步操作存在不一致的可能)
    b.Lua脚本:lua原子性+事务
        【Lua 脚本将 SETNX 和 EXPIRE 操作合并为一个原子操作】,避免了由于客户端故障导致的死锁问题,并确保了锁的超时自动释放
        使用Lua脚本在释放锁时验证唯一标识,确保只有持有锁的客户端才能释放锁,避免误释放问题

1.24 [5]Redisson锁:定义

01.分布式锁
    a.定义
        利用Redis实现的分布式锁,用于在分布式系统中协调多个进程或线程对共享资源的访问
    b.实现
        通过Redis的SET命令设置一个带有过期时间的键来实现锁定,确保只有一个客户端可以获取到锁
    c.应用场景
        用于防止多个实例同时修改共享资源,如数据库记录、文件等

02.分布式计数器
    a.定义
        利用Redis的原子递增操作实现的分布式计数器,用于在分布式系统中统计事件发生的次数
    b.实现
        使用Redis的INCR或INCRBY命令实现计数器的递增
    c.应用场景
        用于统计网站访问量、API调用次数等

03.分布式队列
    a.定义
        利用Redis的列表数据结构实现的分布式队列,用于在分布式系统中实现任务的异步处理
    b.实现
        使用Redis的LPUSH和RPOP命令实现队列的入队和出队操作
    c.应用场景
        用于任务调度、消息传递等

04.分布式信号量
    a.定义
        利用Redis实现的分布式信号量,用于控制对共享资源的并发访问
    b.实现
        通过Redis的原子操作和Lua脚本实现信号量的获取和释放
    c.应用场景
        用于限制同时访问某个资源的客户端数量

05.分布式缓存同步
    a.定义
        利用Redis的发布/订阅机制实现的分布式缓存同步,用于在多个缓存实例之间同步数据
    b.实现
        使用Redis的PUBLISH和SUBSCRIBE命令实现消息的发布和订阅
    c.应用场景
        用于在多个应用实例之间同步缓存数据,确保数据一致性

1.25 [5]Redisson锁:缺点

01.汇总
    a.单点故障
        如果Redis节点宕机,可能导致锁失效
    b.网络延迟
        网络延迟可能导致锁的过期时间不准确
    c.锁过期问题
        如果锁的持有者在锁过期前未能续约,可能导致锁被其他线程获取

02.哨兵模式/主从模式
    a.缺陷
        如果master实例宕机的时候,可能导致多个客户端同时完成加锁
    b.原理
        客户端1 对某个master节点写入了redisson锁,此时会异步复制给对应的 slave节点
        但是这个过程中一旦发生 master节点宕机,主备切换,slave节点从变为了 master节点
        这时客户端2 来尝试加锁的时候,在新的 master节点上也能加锁,此时就会导致多个客户端对同一个分布式锁完成了加锁
        这时系统在业务语义上一定会出现问题,导致各种脏数据的产生

1.26 [5]Redisson锁:流程

01.线程去获取锁,获取成功
    执行lua脚本,保存数据到redis数据库

02.线程去获取锁,获取失败
    一直通过while循环尝试获取锁,获取成功后,执行lua脚本,保存数据到redis数据库

1.27 [5]Redisson锁:stream流

00.总结
    a.说明
        在多线程环境下,使用Redis的集合时
        如果代码中存在对这个集合的增删改操作,应尽量避免使用stream流遍历这个集合,对于java中的普通集合也是同理
    b.解决方案
        在cacheAndGetMessages方法入口对userId加分布式锁(互斥锁),但是因为互斥锁会阻塞,影响性能,不推荐
        使用迭代器遍历。使用迭代器不用先获取List的大小,每遍历一个元素会通过hasNext()判断是否有下一个元素,但是这个场景下也可能会多读取元素
        推荐:上面几种方案都需要发送多个redis命令,改为使用LEANGE messages:1 -15 -1获取尾部的15个元素

01.示例
    a.代码
        public List<Message> getMessages(String userId) {
                String key = getKey(userId);
                return redissonClient.getDeque(key).stream()
                    .map(item -> JacksonUtils.deserialize(item.toString(), Message.class))
                    .toList();
        }
    b.说明
        使用Redisson,通过stream流读取一个Redis的List结构中的内容并返回一个Java的List
        但是这个过程中居然报了npe(空指针异常),报错的描述是item为null

02.分析
    a.说明
        这个方法其实就是获取用户聊天的上下文。在这之前还有另一个方法,将用户的聊天信息存入上下文
    b.代码
        public class Cache {
            public List<Message> cacheAndGetMessages(String userId, String text) {
                    Message userMsg = new Message("user", text);
                    addMsgCache(userId, userMsg);
                    return getMsgCache(userId);
            }

            public void addMsgCache(String userId, Message message) {
                    String key = getKey(userId);
                    RDeque<String> messages = redissonClient.getDeque(key);
                    messages.addLast(JacksonUtils.serialize(message));

                    int msgCacheSize = 15;
                    // 通过lua脚本原子删除多余消息,防止删除时出现线程安全问题
                    deleteExtraMessages(msgCacheSize, key);

                    // 历史记录保留三个小时
                    redisUtils.expire(key, 60 * 60 * 3 * 1000, TimeUnit.MILLISECONDS);
            }

            public List<Message> getMessages(String userId) {
                    String key = getKey(userId);
                    return redissonClient.getDeque(key).stream()
                    .map(item -> JacksonUtils.deserialize(item.toString(), Message.class))
                    .toList();
            }
        }
    c.说明
        从代码来看,cacheAndGetMessages方法做了两件事,缓存新的message,获取所有的message
        其中缓存新的message时,因为我设置了一个上下文的最大值,也就是msgCacheSize的值为15,当超出这个阈值就会将多余的message删除
        为什么getMessages方法中item会为null,先说结论,是因为出现了线程安全问题

03.并发安全问题的根源
    a.说明
        由于cacheAndGetMessages方法不是原子的,所以某次用户连续发了两条消息,就有可能会出现以下情况:
        图中红色的我称为线程A,蓝色的称为线程B。也就是有这样一种可能,当线程A向List中加入一个message后
        并且删除多余的message前,线程B恰好使用代码中的stream流遍历这个List。但是这样为什么会出现npe呢?
    b.深入分析
        a.说明
            报错后我去查看Redis的这个List,发现并没有真正为null或空的元素
            抱着一定要弄明白的心态,我开始怀疑是stream流底层执行的redis命令的问题
        b.说明
            刚开始,我单纯的以为这段代码会将Redis中的List的所有元素加载到内存中并遍历
            后来我突然想起来,java的stream流本身是不会存储数据的
            于是我便想通过代码查看这个stream流底层执行的redis的命令
            可是Redisson封装的太好了,我没能找到
        c.说明
            再接着,我想到了Redis有一个命令,可以监控每一个执行的Redis命令,就是MONITOR命令(注意不要在生产环境使用)
            为了输出好看,我在我的项目中随便找了一个有@Component修饰的类中加入了以下代码
        d.代码
            @PostConstruct
            public void init() {
                new Thread(() -> {
                    try (Jedis jedis = new Jedis(redisHost, redisPort)) {
                        jedis.auth(redisPassword);
                        jedis.monitor(new JedisMonitor() {
                            @Override
                            public void onCommand(String command) {
                                if (command.contains("message:1")) {
                                    System.out.println("Command: " + command);
                                }
                            }
                        });
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }).start();
            }
        e.说明
            可以看到,stream流本质上是先获取List的长度,然后再通过一个循环逐个获取这个长度以内的集合中的元素
            这样就可以解释为什么会有并发安全问题了:线程A添加一个新的元素到集合中后
            线程B准备开始获取集合中的所有元素,首先执行LLEN命令获取集合大小
            得到16,接着线程A将多余的message删除了,集合真正的大小也变成了15
            当线程B执行LINDEX messages:1 15,也就是获取第16个元素时,就会返回null了

1.28 [5]Redisson锁:3类,6种

00.汇总
    a.公平锁
        a.特性
            顺序性:公平锁按照请求的顺序来分配锁,确保每个请求都能公平地获得锁
            适用场景:适用于需要严格控制锁获取顺序的场景,避免“饥饿”现象
        b.优点
            确保锁的获取顺序,避免某些线程长期得不到锁
        c.缺点
            由于需要维护一个等待队列,性能可能不如非公平锁
        d.说明
            这是公平锁,确保锁的获取顺序是按照请求的顺序进行的
    b.读写锁 = 公平锁 + 非公平锁(默认)
        a.特性
            并发性:允许多个读线程同时持有读锁,但写锁是独占的
            适用场景:适用于读多写少的场景,提高并发性能
        b.优点
            提高读操作的并发性,适合读多写少的应用场景
        c.缺点
            写操作会阻塞所有其他操作(包括读操作),可能导致写操作的延迟
        d.说明
            读写锁可以是公平的也可以是非公平的,具体取决于实现方式。Redisson 提供的读写锁默认是非公平的
    c.联锁 = 公平锁 + 非公平锁(默认)
        a.特性
            多资源锁定:允许同时锁定多个资源,只有当所有资源都被锁定时,锁才算成功
            适用场景:适用于需要同时操作多个共享资源的场景,确保一致性
        b.优点
            确保多个资源的一致性,避免死锁
        c.缺点
            复杂性较高,锁定多个资源时可能导致性能下降
        d.说明
            联锁(MultiLock)本身是一个组合锁,可以包含多个锁实例
            每个锁实例可以是公平锁或非公平锁,具体取决于您创建这些锁实例时的选择。
    d.各种锁
        getLock               适合大多数普通的分布式锁场景,对锁的可靠性和性能有一定要求,但没有特殊的分布式锁需求
        getSpinLock           适用于锁竞争不激烈且锁持有时间短的情况,通过自旋避免线程切换,提高性能,但长时间等待会消耗大量 CPU
        getRedLock            适合对锁的可靠性和容错性要求极高的场景,例如金融交易系统、分布式任务调度等,防止单点故障对锁的影响
        getReadWriteLock      适合读写分离的场景,如分布式数据库、缓存等系统,在读多写少的情况下,优化锁的使用,提高并发性能
        getFairLock           公平锁,会按照请求的顺序来分配锁
        getLock               非公平锁,默认

01.公平锁/非公平锁
    a.定义
        公平锁是一种锁机制,确保锁的获取顺序是按照请求的顺序进行的,类似于排队机制
    b.原理
        公平锁通过维护一个等待队列来保证锁的获取顺序。每个请求都会被放入队列中,只有队列头部的请求才能获取锁
    c.常用API
        RLock lock = redissonClient.getFairLock("lockName");
        lock.lock();
        lock.unlock();
    d.使用步骤
        1.创建 Redisson 客户端
        2.获取一个公平锁实例
        3.使用 lock() 方法加锁
        4.完成操作后使用 unlock() 方法解锁
    e.公平锁
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        RedissonClient redissonClient = Redisson.create(config);

        RLock fairLock = redissonClient.getFairLock("fairLock");
        try {
            fairLock.lock();
            // 执行需要加锁的代码
        } finally {
            fairLock.unlock();
        }
    f.非公平锁
        public class NonFairLockExample {
            public static void main(String[] args) {
                // 配置 Redisson 客户端
                Config config = new Config();
                config.useSingleServer().setAddress("redis://127.0.0.1:6379");
                RedissonClient redisson = Redisson.create(config);

                // 创建非公平锁
                RLock lock = redisson.getLock("myLock");

                // 使用锁
                lock.lock();
                try {
                    // 执行需要加锁的代码
                    System.out.println("Locked by non-fair lock");
                } finally {
                    lock.unlock();
                }

                // 关闭 Redisson 客户端
                redisson.shutdown();
            }
        }

02.读写锁
    a.定义
        读写锁是一种锁机制,允许多个读线程同时访问,但写线程访问时会阻塞其他线程
    b.原理
        读写锁通过区分读锁和写锁来提高并发性。多个读线程可以同时持有读锁,但写锁是独占的
    c.常用API
        RReadWriteLock rwLock = redissonClient.getReadWriteLock("lockName");
        rwLock.readLock().lock();
        rwLock.writeLock().lock();
        rwLock.readLock().unlock();
        rwLock.writeLock().unlock();
    d.使用步骤
        1.创建 Redisson 客户端
        2.获取一个读写锁实例
        3.根据需要获取读锁或写锁
        4.完成操作后解锁
    e.代码示例
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        RedissonClient redissonClient = Redisson.create(config);

        RReadWriteLock rwLock = redissonClient.getReadWriteLock("rwLock");
        RLock readLock = rwLock.readLock();
        RLock writeLock = rwLock.writeLock();

        try {
            readLock.lock();
            // 执行读操作
        } finally {
            readLock.unlock();
        }

        try {
            writeLock.lock();
            // 执行写操作
        } finally {
            writeLock.unlock();
        }

03.联锁
    a.定义
        联锁(MultiLock)是一种锁机制,允许同时锁定多个资源,只有当所有资源都被锁定时,锁才算成功
    b.原理
        联锁通过同时获取多个锁来保证多个资源的一致性,适用于需要同时操作多个共享资源的场景
    c.常用API
        RLock lock1 = redissonClient.getLock("lock1");
        RLock lock2 = redissonClient.getLock("lock2");
        RedissonMultiLock multiLock = new RedissonMultiLock(lock1, lock2);
        multiLock.lock();
        multiLock.unlock();
    d.使用步骤
        1.创建 Redisson 客户端
        2.获取多个锁实例
        3.创建一个联锁实例
        4.使用 lock() 方法同时锁定所有资源
        5.完成操作后使用 unlock() 方法解锁
    e.代码示例
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        RedissonClient redissonClient = Redisson.create(config);

        RLock lock1 = redissonClient.getLock("lock1");
        RLock lock2 = redissonClient.getLock("lock2");

        RedissonMultiLock multiLock = new RedissonMultiLock(lock1, lock2);
        try {
            multiLock.lock();
            // 执行需要同时锁定多个资源的代码
        } finally {
            multiLock.unlock();
        }

04.各种锁
    a.getLock
        a.特点
            这是最常用的获取锁的方式,提供了一个可重入锁,支持公平锁和非公平锁
            可重入锁意味着一个线程可以多次获取同一个锁而不会产生死锁,只要它每次都能正确释放锁
            它可以设置锁的超时时间,以防止死锁情况
        b.用法
            import org.redisson.Redisson;
            import org.redisson.api.RLock;
            import org.redisson.api.RedissonClient;
            import org.redisson.config.Config;

            public class RedissonGetLockExample {
                public static void main(String[] args) {
                    Config config = new Config();
                    config.useSingleServer().setAddress("redis://127.0.0.1:6379");
                    RedissonClient redisson = Redisson.create(config);
                    // 获取锁对象
                    RLock lock = redisson.getLock("myLock");
                    try {
                        // 加锁,可设置超时时间,防止死锁
                        lock.lock(10, TimeUnit.SECONDS);
                        // 执行需要加锁的业务逻辑
                        System.out.println("Business logic under getLock");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        // 解锁
                        lock.unlock();
                    }
                    redisson.shutdown();
                }
            }
        c.说明
            上述代码通过 redisson.getLock("myLock") 获取一个普通的可重入锁
            使用 lock(10, TimeUnit.SECONDS) 加锁并设置 10 秒超时时间
            在 finally 块中解锁。它适用于一般的分布式资源竞争场景,如多个线程或服务需要互斥访问某个共享资源
    b.getSpinLock
        a.特点
            这是一种自旋锁,当线程尝试获取锁失败时,会不断循环尝试获取锁,而不是阻塞线程
            自旋锁在锁持有时间较短的情况下,可以减少线程上下文切换的开销,但如果锁持有时间较长,会消耗大量的 CPU 资源
        b.用法
            import org.redisson.Redisson;
            import org.redisson.api.RLock;
            import org.redisson.api.RedissonClient;
            import org.redisson.config.Config;

            public class RedissonGetSpinLockExample {
                public static void main(String[] args) {
                    Config config = new Config();
                    config.useSingleServer().setAddress("redis://127.0.0.1:6379");
                    RedissonClient redisson = Redisson.create(config);
                    // 获取自旋锁对象
                    RLock spinLock = redisson.getSpinLock("mySpinLock");
                    try {
                        // 加锁
                        spinLock.lock();
                        // 执行需要加锁的业务逻辑
                        System.out.println("Business logic under getSpinLock");
                    } finally {
                        // 解锁
                        spinLock.unlock();
                    }
                    redisson.shutdown();
                }
            }
        c.说明
            在这个示例中,使用 redisson.getSpinLock("mySpinLock") 获取自旋锁,调用 lock() 方法尝试加锁
            线程会在获取锁失败时进行自旋等待。适用于锁竞争不激烈且锁持有时间较短的场景
            例如在高并发下对一些快速操作的资源进行加锁
    c.getRedLock
        a.特点
            RedLock 是一种基于多个 Redis 节点的分布式锁算法,用于解决单点故障和数据不一致的问题
            它会在多个 Redis 节点上同时尝试获取锁,只有在大多数节点上成功获取锁时才认为加锁成功,提高了锁的可靠性
        b.用法
            import org.redisson.Redisson;
            import org.redisson.api.RLock;
            import org.redisson.api.RedissonClient;
            import org.redisson.api.RedissonReactiveClient;
            import org.redisson.api.RedissonRxClient;
            import org.redisson.config.Config;

            public class RedissonGetRedLockExample {
                public static void main(String[] args) {
                    Config config = new Config();
                    config.useSingleServer().setAddress("redis://127.0.0.1:6379");
                    RedissonClient redissonClient1 = Redisson.create(config);
                    // 假设这里有多个 Redisson 客户端连接不同的 Redis 节点
                    RedissonClient redissonClient2 = Redisson.create(config);
                    RedissonClient redissonClient3 = Redisson.create(config);

                    RLock lock1 = redissonClient1.getLock("myRedLock");
                    RLock lock2 = redissonClient2.getLock("myRedLock");
                    RLock lock3 = redissonClient3.getLock("myRedLock");

                    RLock redLock = redisson.getRedLock(lock1, lock2, lock3);
                    try {
                        // 加锁
                        redLock.lock();
                        // 执行需要加锁的业务逻辑
                        System.out.println("Business logic under getRedLock");
                    } finally {
                        // 解锁
                        redLock.unlock();
                    }
                    redissonClient1.shutdown();
                    redissonClient2.shutdown();
                    redissonClient3.shutdown();
                }
            }
        c.说明
            在这个例子中,首先创建多个 Redisson 客户端连接不同的 Redis 节点
            然后通过 redisson.getRedLock(lock1, lock2, lock3) 生成一个 RedLock
            它会在多个节点上尝试加锁。适用于对锁的可靠性要求极高,不能容忍单点故障的场景
            比如在分布式系统中对关键资源的保护,需要保证锁的强一致性和高可用性
    d.getReadWriteLock
        a.特点
            提供了读写锁功能,读写锁允许多个读操作同时进行,但写操作是独占的,读写互斥
            读锁可以被多个线程同时持有,但写锁只能被一个线程持有,且写锁的优先级高于读锁
        b.用法
            import org.redisson.Redisson;
            import org.redisson.api.RLock;
            import org.redisson.api.RReadWriteLock;
            import org.redisson.api.RedissonClient;
            import org.redisson.config.Config;

            public class RedissonGetReadWriteLockExample {
                public static void main(String[] args) {
                    Config config = new Config();
                    config.useSingleServer().setAddress("redis://127.0.0.1:6379");
                    RedissonClient redisson = Redisson.create(config);
                    // 获取读写锁对象
                    RReadWriteLock readWriteLock = redisson.getReadWriteLock("myReadWriteLock");
                    // 获取写锁
                    RLock writeLock = readWriteLock.writeLock();
                    try {
                        // 加写锁
                        writeLock.lock();
                        // 执行写操作的业务逻辑
                        System.out.println("Write operation under getReadWriteLock");
                    } finally {
                        // 解锁
                        writeLock.unlock();
                    }
                    // 获取读锁
                    RLock readLock = readWriteLock.readLock();
                    try {
                        // 加读锁
                        readLock.lock();
                        // 执行读操作的业务逻辑
                        System.out.println("Read operation under getReadWriteLock");
                    } finally {
                        // 解锁
                        readLock.unlock();
                    }
                    redisson.shutdown();
                }
            }
        c.说明
            在上述代码中,使用 redisson.getReadWriteLock("myReadWriteLock") 获取读写锁对象
            通过 writeLock() 获取写锁,通过 readLock() 获取读锁。适用于读多写少的场景
            例如在分布式缓存中,多个线程可以同时读取数据,但写数据时需要独占锁,以保证数据的一致性
    e.getFairLock公平锁
        a.思路
            首先创建 Redisson 的配置对象 Config
            使用 config.useSingleServer() 或相应的集群、哨兵模式配置方法设置 Redis 服务器信息
            创建 Redisson 客户端
            通过 redisson.getFairLock("yourLockName") 获取公平锁对象
            调用 lock() 方法加锁,执行需要加锁的业务逻辑
            在 finally 块中使用 unlock() 方法解锁,确保锁被正确释放
        b.代码
            import org.redisson.Redisson;
            import org.redisson.api.RLock;
            import org.redisson.api.RedissonClient;
            import org.redisson.config.Config;

            public class RedissonFairLockExample {
                public static void main(String[] args) {
                    // 创建 Redisson 配置对象
                    Config config = new Config();
                    // 假设使用单点 Redis 服务器,设置服务器地址
                    config.useSingleServer().setAddress("redis://127.0.0.1:6379");
                    // 创建 Redisson 客户端
                    RedissonClient redisson = Redisson.create(config);
                    // 获取公平锁对象,指定锁名称
                    RLock fairLock = redisson.getFairLock("myFairLock");
                    try {
                        // 加锁
                        fairLock.lock();
                        // 执行需要加锁的业务逻辑
                        System.out.println("Business logic under fair lock");
                    } finally {
                        // 解锁
                        fairLock.unlock();
                    }
                    // 关闭 Redisson 客户端
                    redisson.shutdown();
                }
            }
        c.说明
            Config config = new Config();:创建一个 Redisson 的配置对象,用于后续的配置
            config.useSingleServer().setAddress("redis://127.0.0.1:6379");:设置 Redis 服务器为单点模式,并指定服务器的地址,你可以根据实际情况修改该地址
            RedissonClient redisson = Redisson.create(config);:使用配置对象创建 Redisson 客户端
            RLock fairLock = redisson.getFairLock("myFairLock");:通过 redisson.getFairLock("myFairLock") 方法获取一个名为 myFairLock 的公平锁对象。公平锁的特点是按照请求锁的顺序来分配锁,避免了某些线程长时间等待锁的情况,保证了锁分配的公平性
            fairLock.lock();:调用 lock() 方法加锁,若当前锁已被其他线程持有,该线程会等待,直到获取到锁
            在 finally 块中调用 fairLock.unlock(); 解锁,确保锁能正常释放,避免死锁和资源泄漏
            redisson.shutdown();:关闭 Redisson 客户端,释放相关资源
        d.说明
            你可以根据需要修改 setAddress 中的 Redis 服务器地址,使其符合你的 Redis 部署
            对于锁的名称 myFairLock,可以根据不同的业务场景进行修改,确保不同的业务使用不同的锁名称,避免锁冲突
            在 try 块中添加你需要加锁的业务逻辑,如数据库操作、资源访问等,确保这些操作在加锁期间是线程安全的
    f.getLock非公平锁
        a.定义
            公平锁是一种锁机制,确保锁的获取顺序是按照请求的顺序进行的,类似于排队机制
        b.原理
            公平锁通过维护一个等待队列来保证锁的获取顺序。每个请求都会被放入队列中,只有队列头部的请求才能获取锁
        c.常用API
            RLock lock = redissonClient.getFairLock("lockName");
            lock.lock();
            lock.unlock();
        d.使用步骤
            1.创建 Redisson 客户端
            2.获取一个公平锁实例
            3.使用 lock() 方法加锁
            4.完成操作后使用 unlock() 方法解锁
        e.代码示例
            import org.redisson.api.RLock;
            import org.redisson.api.RedissonClient;
            import org.redisson.Redisson;
            import org.redisson.config.Config;

            public class NonFairLockExample {
                public static void main(String[] args) {
                    // 配置 Redisson 客户端
                    Config config = new Config();
                    config.useSingleServer().setAddress("redis://127.0.0.1:6379");
                    RedissonClient redisson = Redisson.create(config);

                    // 创建非公平锁
                    RLock lock = redisson.getLock("myLock");

                    // 使用锁
                    lock.lock();
                    try {
                        // 执行需要加锁的代码
                        System.out.println("Locked by non-fair lock");
                    } finally {
                        lock.unlock();
                    }

                    // 关闭 Redisson 客户端
                    redisson.shutdown();
                }
            }

1.29 [5]Redisson锁:RLock

00.汇总
    a.常用API
        RedissonClient:Redisson的核心接口,用于创建分布式对象
        RLock:Redisson提供的分布式可重入锁接口
    b.流程概况
        A、B线程争抢一把锁,A获取到后,B阻塞
        B线程阻塞时并非主动 CAS,而是PubSub方式订阅该锁的广播消息
        A操作完成释放了锁,B线程收到订阅消息通知
        B被唤醒开始继续抢锁,拿到锁
    c.使用步骤
        创建RedissonClient实例
        获取RLock实例
        使用RLock进行加锁、解锁和续约操作
    d.代码示例
        import org.redisson.Redisson;
        import org.redisson.api.RLock;
        import org.redisson.api.RedissonClient;
        import org.redisson.config.Config;

        public class RedissonExample {

            public static void main(String[] args) {
                // 创建Redisson配置
                Config config = new Config();
                config.useSingleServer().setAddress("redis://127.0.0.1:6379");

                // 创建Redisson客户端
                RedissonClient redisson = Redisson.create(config);

                // 获取RLock实例
                RLock lock = redisson.getLock("myLock");

                try {
                    // 加锁
                    lock.lock();

                    // 执行业务逻辑
                    System.out.println("Lock acquired, executing business logic...");

                } finally {
                    // 解锁
                    lock.unlock();
                    System.out.println("Lock released.");
                }

                // 关闭Redisson客户端
                redisson.shutdown();
            }
        }

01.加锁
    a.说明
        a.定义
            RLock是Redisson提供的分布式可重入锁
        b.原理
            通过Redis的SET命令和Lua脚本实现加锁
    b.源码分析
        a.开始
            RLock是Redisson分布式锁的最核心接口,继承了concurrent包的Lock接口和自己的RLockAsync接口
            RLockAsync的返回值都是RFuture,是Redisson执行异步实现的核心逻辑,也是Netty发挥的主要阵地
        b.源码
            从RLock进入,找到RedissonLock类,找到 tryLock 方法再递进到干活的tryAcquireOnceAsync 方法,这是加锁的主要代码
            private RFuture<Boolean> tryAcquireOnceAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
                    if (leaseTime != -1L) {
                        return this.tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
                    } else {
                        RFuture<Boolean> ttlRemainingFuture = this.tryLockInnerAsync(waitTime, this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
                        ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
                            if (e == null) {
                                if (ttlRemaining) {
                                    this.scheduleExpirationRenewal(threadId);
                                }

                            }
                        });
                        return ttlRemainingFuture;
                    }
                }
        c.继续
            此处出现leaseTime时间判断的2个分支,实际上就是加锁时是否设置过期时间,未设置过期时间(-1)时则会有watchDog 的锁续约,
            一个注册了加锁事件的续约任务。我们先来看有过期时间tryLockInnerAsync 部分
        d.源码
            evalWriteAsync是eval命令执行lua的入口
            <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
                    this.internalLockLeaseTime = unit.toMillis(leaseTime);
                    return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command, "if (redis.call('exists', KEYS[1]) == 0) then redis.call('hset', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; return redis.call('pttl', KEYS[1]);", Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});
                }

02.解锁
    a.说明
        a.定义
            解锁是释放已持有的锁
        b.原理
            通过Lua脚本检查锁的持有者,并在确认后删除锁
    b.源码分析
        a.开始
            这里只有一个明显的监听方法onMessage,其订阅和信号量的释放都在父类PublishSubscribe,我们只关注监听事件的实际操作
        b.源码
            protected void onMessage(RedissonLockEntry value, Long message) {
                Runnable runnableToExecute;
                 if (message.equals(unlockMessage)) {
                     // 从监听器队列取监听线程执行监听回调
                     runnableToExecute = (Runnable)value.getListeners().poll();
                     if (runnableToExecute != null) {
                         runnableToExecute.run();
                     }
                     // getLatch()返回的是Semaphore,信号量,此处是释放信号量
                     // 释放信号量后会唤醒等待的entry.getLatch().tryAcquire去再次尝试申请锁
                     value.getLatch().release();
                 } else if (message.equals(readUnlockMessage)) {
                     while(true) {
                         runnableToExecute = (Runnable)value.getListeners().poll();
                         if (runnableToExecute == null) {
                             value.getLatch().release(value.getLatch().getQueueLength());
                             break;
                         }
                         runnableToExecute.run();
                     }
                 }
             }
        c.继续
            发现一个是默认解锁消息 ,一个是读锁解锁消息 ,因为redisson是有提供读写锁的
            而读写锁读读情况和读写、写写情况互斥情况不同,我们只看上面的默认解锁消息unlockMessage分支
            LockPubSub监听最终执行了2件事
            runnableToExecute.run() 执行监听回调
            value.getLatch().release(); 释放信号量
        d.源码
            Redisson通过LockPubSub 监听解锁消息,执行监听回调和释放信号量通知等待线程可以重新抢锁
            这时再回来看tryAcquireOnceAsync另一分支
            private RFuture<Boolean> tryAcquireOnceAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
                    if (leaseTime != -1L) {
                        return this.tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
                    } else {
                        RFuture<Boolean> ttlRemainingFuture = this.tryLockInnerAsync(waitTime, this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
                        ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
                            if (e == null) {
                                if (ttlRemaining) {
                                    this.scheduleExpirationRenewal(threadId);
                                }

                            }
                        });
                        return ttlRemainingFuture;
                    }
                }

03.锁续约
    a.说明
        a.定义
            锁续约是延长锁的过期时间,防止锁在持有期间意外过期
        b.原理
            Redisson的Watch Dog机制自动处理锁续约
    b.源码分析
        a.开始
            查看RedissonLock.this.scheduleExpirationRenewal(threadId)
        b.源码
            private void scheduleExpirationRenewal(long threadId) {
                    RedissonLock.ExpirationEntry entry = new RedissonLock.ExpirationEntry();
                    RedissonLock.ExpirationEntry oldEntry = (RedissonLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.putIfAbsent(this.getEntryName(), entry);
                    if (oldEntry != null) {
                        oldEntry.addThreadId(threadId);
                    } else {
                        entry.addThreadId(threadId);
                        this.renewExpiration();
                    }

                }

            private void renewExpiration() {
                    RedissonLock.ExpirationEntry ee = (RedissonLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.get(this.getEntryName());
                    if (ee != null) {
                        Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
                            public void run(Timeout timeout) throws Exception {
                                RedissonLock.ExpirationEntry ent = (RedissonLock.ExpirationEntry)RedissonLock.EXPIRATION_RENEWAL_MAP.get(RedissonLock.this.getEntryName());
                                if (ent != null) {
                                    Long threadId = ent.getFirstThreadId();
                                    if (threadId != null) {
                                        RFuture<Boolean> future = RedissonLock.this.renewExpirationAsync(threadId);
                                        future.onComplete((res, e) -> {
                                            if (e != null) {
                                                RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName() + " expiration", e);
                                            } else {
                                                if (res) {
                                                    RedissonLock.this.renewExpiration();
                                                }

                                            }
                                        });
                                    }
                                }
                            }
                        }, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
                        ee.setTimeout(task);
                    }
                }
        c.继续
            拆分来看,这段连续嵌套且冗长的代码实际上做了几步:
            添加一个netty的Timeout回调任务,每(internalLockLeaseTime / 3)毫秒执行一次,执行的方法是renewExpirationAsync
            renewExpirationAsync重置了锁超时时间,又注册一个监听器,监听回调又执行了renewExpiration
        d.源码
            renewExpirationAsync 的Lua如下
            protected RFuture<Boolean> renewExpirationAsync(long threadId) {
                    return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('pexpire', KEYS[1], ARGV[1]); return 1; end; return 0;", Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});
                }

            if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
              redis.call('pexpire', KEYS[1], ARGV[1]);
              return 1;
            end;
            return 0;
        e.说明
            重新设置了超时时间。
            Redisson加这段逻辑的目的是什么?
            目的是为了某种场景下保证业务不影响,如任务执行超时但未结束,锁已经释放的问题
            当一个线程持有了一把锁,由于并未设置超时时间leaseTime,Redisson 默认配置了30S,开启watchDog,每10S对该锁进行一次续约,维持30S的超时时间,直到任务完成再删除锁
            这就是Redisson的锁续约 ,也就是WatchDog 实现的基本思路

1.30 [5]Redisson锁:RStream

01.Redis Streams和Redisson
    a.描述
        Redis Streams是一种数据结构,它模拟了日志数据类型,允许多个生产者和消费者以高度并发的方式读写数据
        Redisson是一个Java内存数据网格,提供了分布式和可扩展的数据结构和服务
        它的特点之一就是能够以Java友好的方式与Redis Streams进行交互
    b.说明
        Redis 5.0引入的Redis Streams为实现事件驱动架构提供了坚实的基础
        在Spring应用程序中,可以通过封装常见操作的工具类来简化Redis Streams的集成
        本文介绍了RedisStreamUtil,这是一个为Spring应用程序设计的工具类,它简化了与Redis Streams的交互

02.RedisStreamUtil工具类
    a.描述
        RedisStreamUtil是一个工具类,旨在抽象与Redis Streams交互的复杂性
        它提供了创建流和消费者组、向流中添加事件、读取事件和确认事件处理完成的方法
    b.代码示例
        import org.redisson.api.RStream;
        import org.redisson.api.RedissonClient;
        import org.redisson.api.StreamMessageId;
        import org.redisson.api.stream.StreamAddArgs;
        import org.redisson.api.stream.StreamReadGroupArgs;
        import org.redisson.api.stream.TrimStrategy;
        import org.redisson.client.RedisException;
        import org.slf4j.Logger;
        import org.slf4j.LoggerFactory;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.stereotype.Service;

        import java.util.Map;

        @Service
        public class RedisStreamUtil {

            private static final Logger logger = LoggerFactory.getLogger(RedisStreamUtil.class);
            @Autowired
            private RedissonClient redissonClient;

            // 创建流和消费者组,如果它们还不存在
            public void createStreamAndConsumerGroup(String streamName, String groupName) {
                RStream<String, String> stream = redissonClient.getStream(streamName);
                try {
                    // 尝试创建消费者组,如果流不存在,它将被自动创建
                    stream.createGroup(groupName);
                } catch (RedisException e) {
                    // 如果消费者组已经存在,我们将得到一个错误
                    if (e.getMessage().contains("BUSYGROUP Consumer Group name already exists")) {
                        logger.info("Consumer group '{}' already exists for stream '{}'", groupName, streamName);
                    } else {
                        // 如果是其他错误,记录日志
                        logger.error("Error creating consumer group '{}' for stream '{}'", groupName, streamName, e);
                    }
                }
            }

            // 添加事件到流
            public StreamMessageId addEventToStream(String streamName, Map<String, String> event) {
                try {
                    RStream<String, String> stream = redissonClient.getStream(streamName);
                    return stream.add(StreamAddArgs.entries(event).trim(TrimStrategy.MAXLEN, 1000));
                } catch (Exception e) {
                    logger.error("Error adding event to stream", e);
                    return null;
                }
            }

            // 读取事件
            public Map<StreamMessageId, Map<String, String>> readEventsFromStream(String streamName, String groupName, String consumerName) {
                try {
                    RStream<String, String> stream = redissonClient.getStream(streamName);
                    return stream.readGroup(groupName, consumerName, StreamReadGroupArgs.neverDelivered());
                } catch (Exception e) {
                    logger.error("Error reading events from stream", e);
                    return null;
                }
            }

            // 确认事件处理完成
            public void acknowledgeEvent(String streamName, String groupName, StreamMessageId messageId) {
                try {
                    RStream<String, String> stream = redissonClient.getStream(streamName);
                    stream.ack(groupName, messageId);
                } catch (Exception e) {
                    logger.error("Error acknowledging event", e);
                }
            }

            // 读取并自动确认事件
            public Map<StreamMessageId, Map<String, String>> readAndAcknowledgeEventsFromStream(String streamName, String groupName, String consumerName) {
                Map<StreamMessageId, Map<String, String>> messages = readEventsFromStream(streamName, groupName, consumerName);
                if (messages != null) {
                    messages.keySet().forEach(messageId -> acknowledgeEvent(streamName, groupName, messageId));
                }
                return messages;
            }

            // 允许自定义 StreamAddArgs
            public StreamMessageId addEventToStreams(String streamName, Map<String, String> event) {
                try {
                    RStream<String, String> stream = redissonClient.getStream(streamName);
                    StreamAddArgs<String, String> streamAddArgs = StreamAddArgs.<String, String>entries(event);
                    return stream.add(streamAddArgs);
                } catch (Exception e) {
                    logger.error("Error adding event to stream", e);
                    return null;
                }
            }

            // 允许自定义 StreamReadGroupArgs
            public Map<StreamMessageId, Map<String, String>> readEventsFromStream(String streamName, String groupName, String consumerName, StreamReadGroupArgs readGroupArgs) {
                try {
                    RStream<String, String> stream = redissonClient.getStream(streamName);
                    return stream.readGroup(groupName, consumerName, readGroupArgs);
                } catch (Exception e) {
                    logger.error("Error reading events from stream with custom StreamReadGroupArgs", e);
                    return null;
                }
            }
        }
    c.测试类
        import org.redisson.api.StreamMessageId;
        import org.redisson.api.stream.StreamReadGroupArgs;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.stereotype.Service;

        import java.util.HashMap;
        import java.util.Map;

        @Service
        public class MyStreamService {

            @Autowired
            private RedisStreamUtil redisStreamUtil;

            public void processStream() {
                String streamName = "myStream";
                String groupName = "myGroup";
                String consumerName = "myConsumer";

                // 创建流和消费者组
                redisStreamUtil.createStreamAndConsumerGroup(streamName, groupName);

                // 添加事件到流
                Map<String, String> event = new HashMap<>();
                event.put("eventKey1", "eventValue1");
                event.put("eventKey2", "eventValue2");
                StreamMessageId messageId = redisStreamUtil.addEventToStream(streamName, event);

                // 读取事件
                Map<StreamMessageId, Map<String, String>> messages = redisStreamUtil.readEventsFromStream(streamName, groupName, consumerName);

                // 处理事件并确认
                if (messages != null) {
                    for (Map.Entry<StreamMessageId, Map<String, String>> messageEntry : messages.entrySet()) {
                        StreamMessageId msgId = messageEntry.getKey();
                        Map<String, String> msgBody = messageEntry.getValue();
                        System.out.println("Processing message with ID: " + msgId + " and body: " + msgBody);

                        // 确认事件处理完成
                        redisStreamUtil.acknowledgeEvent(streamName, groupName, msgId);
                    }
                }

                // 使用自定义 StreamReadGroupArgs 读取事件
                StreamReadGroupArgs readGroupArgs = StreamReadGroupArgs.neverDelivered().count(10);
                Map<StreamMessageId, Map<String, String>> customMessages = redisStreamUtil.readEventsFromStream(streamName, groupName, consumerName, readGroupArgs);

                // 处理自定义参数读取的事件...
            }
        }

1.31 [5]Redisson锁:RedLock

01.定义
    a.说明
        RedLock 是一种分布式锁的实现算法,主要用于解决在分布式系统中实现可靠锁的问题
        在 Redis 单独节点的基础上,RedLock 使用了多个独立的 Redis 实例(通常建议是奇数个,比如 5 个)
        共同协作来提供更强健的分布式锁服务
    b.宗旨
        解决单个 Redis 实例作为分布式锁时可能出现的单点故障问题
        通过在多个独立运行的 Redis 实例上同时获取锁的方式来提高锁服务的可用性和安全性

02.主要特性
    a.互斥性
        在任何时间,只有一个客户端可以获得锁,确保了资源的互斥访问
    b.避免死锁
        通过为锁设置一个较短的过期时间,即使客户端在获得锁后由于网络故障等原因未能按时释放锁
        锁也会因为过期而自动释放,避免了死锁的发生
    c.容错性
        即使一部分 Redis 节点宕机,只要大多数节点(即过半数以上的节点)仍在线
        RedLock 算法就能继续提供服务,并确保锁的正确性

03.两个问题
    a.性能问题
        RedLock 要等待大多数节点返回之后,才能加锁成功
        而这个过程中可能会因为网络问题,或节点超时的问题,影响加锁的性能
    b.并发安全性问题
        当客户端加锁时,如果遇到 GC 可能会导致加锁失效,但 GC 后误认为加锁成功的安全事故,例如以下流程:
        1.客户端 A 请求 3 个节点进行加锁
        2.在节点回复处理之前,客户端 A 进入 GC 阶段(存在 STW,全局停顿)
        3.之后因为加锁时间的原因,锁已经失效了
        4.客户端 B 请求加锁(和客户端 A 是同一把锁),加锁成功
        5.客户端 A GC 完成,继续处理前面节点的消息,误以为加锁成功
        6.此时客户端 B 和客户端 A 同时加锁成功,出现并发安全性问题
    c.已废弃的RedLock
        因为 RedLock 存在的问题争议较大,且没有完美的解决方案,所以 Redisson 中已经废弃了 RedLock

04.代码示例
    a.实现思路
        RedLock 是对集群的每个节点进行加锁,如果大多数节点(N/2+1)加锁成功,则才会认为加锁成功
        这样即使集群中有某个节点挂掉了,因为大部分集群节点都加锁成功了,所以分布式锁还是可以继续使用的
    b.工作流程
        1.客户端向多个独立的 Redis 实例尝试获取锁,设置锁的过期时间非常短
        2.如果客户端能在大部分节点上成功获取锁,并且所花费的时间小于锁的过期时间的一半,那么认为客户端成功获取到了分布式锁
        3.当客户端完成对受保护资源的操作后,它需要向所有曾获取锁的 Redis 实例释放锁
        4.若在释放锁的过程中,客户端因故无法完成,由于设置了锁的过期时间,锁最终会自动过期释放,避免了死锁
    c.Redisson实现RedLock
        import org.redisson.Redisson;
        import org.redisson.api.RedisClient;
        import org.redisson.api.RedissonClient;
        import org.redisson.config.Config;
        import org.redisson.redisson.RedissonRedLock;

        public class RedLockDemo {

            public static void main(String[] args) {
                // 创建 Redisson 客户端配置
                Config config = new Config();
                config.useClusterServers()
                .addNodeAddress("redis://127.0.0.1:6379",
                                "redis://127.0.0.1:6380",
                                "redis://127.0.0.1:6381"); // 假设有三个 Redis 节点
                // 创建 Redisson 客户端实例
                RedissonClient redissonClient = Redisson.create(config);
                // 创建 RedLock 对象
                RedissonRedLock redLock = redissonClient.getRedLock("resource");
                try {
                    // 尝试获取分布式锁,最多尝试 5 秒获取锁,并且锁的有效期为 5000 毫秒
                    boolean lockAcquired = redLock.tryLock(5, 5000, TimeUnit.MILLISECONDS);
                    if (lockAcquired) {
                        // 加锁成功,执行业务代码...
                    } else {
                        System.out.println("Failed to acquire the lock!");
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    System.err.println("Interrupted while acquiring the lock");
                } finally {
                    // 无论是否成功获取到锁,在业务逻辑结束后都要释放锁
                    if (redLock.isLocked()) {
                        redLock.unlock();
                    }
                    // 关闭 Redisson 客户端连接
                    redissonClient.shutdown();
                }
            }
        }

05.实现原理
    a.说明
        Redisson 中的 RedLock 是基于 RedissonMultiLock(联锁)实现
    b.RedissonMultiLock
        RedissonMultiLock 是 Redisson 提供的一种分布式锁类型,它可以同时操作多个锁
        以达到对多个锁进行统一管理的目的。联锁的操作是原子性的,即要么全部锁住,要么全部解锁
        这样可以保证多个锁的一致性
    c.RedissonMultiLock代码示例
        import org.redisson.Redisson;
        import org.redisson.api.RLock;
        import org.redisson.api.RedissonClient;
        import org.redisson.config.Config;
        import org.redisson.multi.MultiLock;

        public class RedissonMultiLockDemo {

            public static void main(String[] args) throws InterruptedException {
                // 创建 Redisson 客户端
                Config config = new Config();
                config.useSingleServer().setAddress("redis://127.0.0.1:6379");
                RedissonClient redisson = Redisson.create(config);

                // 创建多个分布式锁实例
                RLock lock1 = redisson.getLock("lock1");
                RLock lock2 = redisson.getLock("lock2");
                RLock lock3 = redisson.getLock("lock3");

                // 创建 RedissonMultiLock 对象
                MultiLock multiLock = new MultiLock(lock1, lock2, lock3);

                // 加锁
                multiLock.lock();
                try {
                    // 执行任务
                    System.out.println("Lock acquired. Task started.");
                    Thread.sleep(3000);
                    System.out.println("Task finished. Releasing the lock.");
                } finally {
                    // 释放锁
                    multiLock.unlock();
                }
                // 关闭客户端连接
                redisson.shutdown();
            }
        }

1.32 [5]Redisson锁:WatchDog

01.看门狗
    a.说明
        在Redisson实例被关闭前,自动续约锁的过期时间,解决锁过期导致的并发问题
        默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来指定
    b.问题
        a.锁过期问题
            在分布式系统中,使用Redis实现分布式锁时,通常会为锁设置一个过期时间,以防止锁因持有者故障而永远无法释放
            然而,如果持有锁的线程在锁过期前未能完成任务并释放锁,锁可能会过期并被其他线程获取,导致并发问题
        b.场景描述
            假设一个分布式系统中有多个服务实例,其中一个实例获取了分布式锁以执行某项长时间任务
            由于任务执行时间不确定,可能会超过锁的过期时间
            如果锁过期,其他实例可能会获取锁并开始执行同一任务,导致任务重复执行或数据不一致
    c.看门狗机制
        a.自动续约
            Redisson的看门狗机制通过后台线程自动续约锁的过期时间,确保在持有锁期间不会因锁过期而导致锁被其他线程获取
            当一个线程获取到锁后,Redisson会启动一个后台任务,定期检查锁的过期时间,并在必要时自动延长锁的过期时间
        b.灵活性
            看门狗机制使得锁的持有者无需担心锁的过期时间,可以专注于任务的执行
        c.有事
            避免锁过期:确保锁在持有期间不会过期,防止并发问题
            简化开发:开发者无需手动管理锁的续约,减少代码复杂性
            提高可靠性:提高分布式锁的可靠性,确保任务的独占执行

02.看门狗
    a.定义
        Watch Dog是Redisson的自动延期机制,用于防止锁在持有期间意外过期
    b.原理
        当一个线程获取到锁后,Redisson会启动一个后台任务,定期检查锁的过期时间,并在必要时自动延长锁的过期时间
    c.场景
        在一个分布式环境下,假如一个线程获得锁后,突然服务器宕机了,那么这个时候在一定时间后这个锁会自动释放
        你也可以设置锁的有效时间(不设置默认30秒),这样的目的主要是防止死锁的发生
    d.但在实际开发中会有下面一种情况
        //设置锁1秒过去
        redissonLock.lock("redisson", 1);
        /**
         * 业务逻辑需要咨询2秒
         */
        redissonLock.release("redisson");
        /**
        * 线程1 进来获得锁后,线程一切正常并没有宕机,但它的业务逻辑需要执行2秒,这就会有个问题,在 线程1 执行1秒后,这个锁就自动过期了,
        * 那么这个时候 线程2 进来了。那么就存在 线程1和线程2 同时在这段业务逻辑里执行代码,这当然是不合理的。
        * 而且如果是这种情况,那么在解锁时系统会抛异常,因为解锁和加锁已经不是同一线程了,具体后面代码演示。
        */
        所以这个时候看门狗就出现了,它的作用就是 线程1 业务还没有执行完,时间就过了
        线程1 还想持有锁的话,就会启动一个 watch dog后台线程,不断的延长锁 key的生存时间
        注意:正常这个看门狗线程是不启动的,还有就是这个看门狗启动后对整体性能也会有一定影响,所以不建议开启看门狗

1.33 [5]Redisson锁:RRateLimiter

00.汇总
    普通限流
    自适应限流

01.Bean:普通限流
    a.依赖
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.16.4</version>
        </dependency>
    b.配置
        @Configuration
        public class RedissonConfiguration {
            @Bean(name = "rateLimiterRedissonClient")
            public RedissonClient getRateLimiterRedissonClient() {
                Config config = new Config();
                String clusterNodes = "redis集群节点信息(127.0.0.1:6379)";
                int connectionMinIdleSize = 10; // 连接池最小空闲线程数
                int connectionMaxSize = 100; // 连接池最大连接数

                if (StringUtils.isBlank(clusterNodes)) {
                    throw new RuntimeException("Redisson 初始化失败, 没有配置集群地址");
                }

                String[] nodes = clusterNodes.split(",");
                List<String> newNodes = new ArrayList<>(nodes.length);
                Arrays.stream(nodes)
                        .forEach((index) -> newNodes.add(index.startsWith("redis://") ? index : "redis://" + index));

                config.useClusterServers()
                        .addNodeAddress(newNodes.toArray(new String[0]))
                        .setMasterConnectionMinimumIdleSize(connectionMinIdleSize)
                        .setSlaveConnectionMinimumIdleSize(connectionMinIdleSize)
                        .setMasterConnectionPoolSize(connectionMaxSize)
                        .setSlaveConnectionPoolSize(connectionMaxSize);

                config.setTransportMode(TransportMode.NIO);
                return Redisson.create(config);
            }
        }
        -----------------------------------------------------------------------------------------------------
        @Configuration
        public class RRateLimiterConfiguration {

            @Autowired
            @Qualifier("rateLimiterRedissonClient")
            private RedissonClient rateLimiterRedissonClient;

            @Bean(name = "RRateLimiter")
            public RRateLimiter getUserTimeGetCoinRRateLimiter() {
                String RRateLimiterName = "限流器名字";
                RRateLimiter RRateLimiter = rateLimiterRedissonClient.getRateLimiter(RRateLimiterName);
                long RRateLimiterFre = 3; // 限流器令牌桶最大大小 也是N秒内发放的令牌桶数量
                long RRateLimiterTime = 5; // N 秒内限制通过令牌的数量的N(时间参数)
                RRateLimiter.trySetRate(RateType.OVERALL,
                        RRateLimiterFre, RRateLimiterTime, RateIntervalUnit.SECONDS);
                return RRateLimiter;
            }
        }
    c.使用
        @Autowired
        @Qualifier("RRateLimiter")
        private RRateLimiter RRateLimiter;

        if (RRateLimiter.tryAcquire(1)) {
            // 执行被限流的业务逻辑
        } else {
            // 被限流后的操作
        }
    d.原理
        a.getRateLimiter
            // 声明一个限流器,名称叫 key
            redissonClient.getRateLimiter(key);
        b.trySetRate方法的底层实现
            @Override
            public RFuture<Boolean> trySetRateAsync(RateType type, long rate, long rateInterval, RateIntervalUnit unit) {
                return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                        "redis.call('hsetnx', KEYS[1], 'rate', ARGV[1]);"
                        + "redis.call('hsetnx', KEYS[1], 'interval', ARGV[2]);"
                        + "return redis.call('hsetnx', KEYS[1], 'type', ARGV[3]);",
                        Collections.<Object>singletonList(getName()), rate, unit.toMillis(rateInterval), type.ordinal());
            }
        c.例如下面这段代码
            5 秒内产生 3 个令牌,并且所有实例共享(RateType.OVERALL 为所有实例共享,RateType.CLIENT 为单实例共享):
            trySetRate(RateType.OVERALL, 3, 5, RateIntervalUnit.SECONDS);
            -------------------------------------------------------------------------------------------------
            那么 Redis 中将会设置 3 个参数:
            hsetnx key rate 3
            hsetnx key interval 5
            hsetnx key type 0
        d.tryAcquire(1)底层源码
            private <T> RFuture<T> tryAcquireAsync(RedisCommand<T> command, Long value) {
                return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
                        "local rate = redis.call('hget', KEYS[1], 'rate');"  //1
                        + "local interval = redis.call('hget', KEYS[1], 'interval');"  //2
                        + "local type = redis.call('hget', KEYS[1], 'type');" //3
                        + "assert(rate ~= false and interval ~= false and type ~= false, 'RateLimiter is not initialized')" //4
                        + "local valueName = KEYS[2];" //5
                        + "if type == 1 then "
                            + "valueName = KEYS[3];" //6
                        + "end;"
                        + "local currentValue = redis.call('get', valueName); " //7
                        + "if currentValue ~= false then "
                               + "if tonumber(currentValue) < tonumber(ARGV[1]) then " //8
                                   + "return redis.call('pttl', valueName); "
                               + "else "
                                   + "redis.call('decrby', valueName, ARGV[1]); " //9
                                   + "return nil; "
                               + "end; "
                        + "else " //10
                               + "redis.call('set', valueName, rate, 'px', interval); "
                               + "redis.call('decrby', valueName, ARGV[1]); "
                               + "return nil; "
                        + "end;",
                        Arrays.<Object>asList(getName(), getValueName(), getClientValueName()),
                        value, commandExecutor.getConnectionManager().getId().toString());
            }
            -------------------------------------------------------------------------------------------------
            既然是令牌桶,为什么没有初始化容量。在这段 Lua 脚本中其实就给出了答案
            在步骤 5、6、7 中,如果发现容量没有设置,就会把 rate 设置为容量

02.注解:普通限流
    a.说明
        a.解决思路
            对登录接口加上限流操作,如限制一分钟内最多登录5次,登录次数过多,就返回失败提示,或者将账号锁定一段时间
        b.实现手段
            利用redis的有序集合即Sorted Set数据结构,构造一个令牌桶来实施限流
            而redisson已经帮我们封装成了RRateLimiter,通过redisson,即可快速实现我们的目标
    b.定义限流注解
        import org.redisson.api.RateIntervalUnit;

        import java.lang.annotation.ElementType;
        import java.lang.annotation.Retention;
        import java.lang.annotation.RetentionPolicy;
        import java.lang.annotation.Target;

        @Target(ElementType.METHOD)
        @Retention(RetentionPolicy.RUNTIME)
        public @interface GlobalRateLimiter {

            String key();

            long rate();

            long rateInterval() default 1L;

            RateIntervalUnit rateIntervalUnit() default RateIntervalUnit.SECONDS;

        }
    c.利用aop进行切面

        import com.zj.demoshow.annotion.GlobalRateLimiter;
        import lombok.extern.slf4j.Slf4j;
        import org.aspectj.lang.ProceedingJoinPoint;
        import org.aspectj.lang.annotation.Around;
        import org.aspectj.lang.annotation.Aspect;
        import org.aspectj.lang.annotation.Pointcut;
        import org.aspectj.lang.reflect.MethodSignature;
        import org.redisson.Redisson;
        import org.redisson.api.RRateLimiter;
        import org.redisson.api.RateIntervalUnit;
        import org.redisson.api.RateType;
        import org.springframework.beans.factory.annotation.Value;
        import org.springframework.core.DefaultParameterNameDiscoverer;
        import org.springframework.expression.Expression;
        import org.springframework.expression.ExpressionParser;
        import org.springframework.expression.spel.standard.SpelExpressionParser;
        import org.springframework.expression.spel.support.StandardEvaluationContext;
        import org.springframework.stereotype.Component;

        import javax.annotation.Resource;
        import java.lang.reflect.Method;
        import java.util.concurrent.TimeUnit;

        @Aspect
        @Component
        @Slf4j
        public class GlobalRateLimiterAspect {

            @Resource
            private Redisson redisson;
            @Value("${spring.application.name}")
            private String applicationName;
            private final DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();

            @Pointcut(value = "@annotation(com.zj.demoshow.annotion.GlobalRateLimiter)")
            public void cut() {
            }

            @Around(value = "cut()")
            public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
                MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
                Method method = methodSignature.getMethod();
                String className = method.getDeclaringClass().getName();
                String methodName = method.getName();
                GlobalRateLimiter globalRateLimiter = method.getDeclaredAnnotation(GlobalRateLimiter.class);
                Object[] params = joinPoint.getArgs();
                long rate = globalRateLimiter.rate();
                String key = globalRateLimiter.key();
                long rateInterval = globalRateLimiter.rateInterval();
                RateIntervalUnit rateIntervalUnit = globalRateLimiter.rateIntervalUnit();
                if (key.contains("#")) {
                    ExpressionParser parser = new SpelExpressionParser();
                    StandardEvaluationContext ctx = new StandardEvaluationContext();
                    String[] parameterNames = discoverer.getParameterNames(method);
                    if (parameterNames != null) {
                        for (int i = 0; i < parameterNames.length; i++) {
                            ctx.setVariable(parameterNames[i], params[i]);
                        }
                    }
                    Expression expression = parser.parseExpression(key);
                    Object value = expression.getValue(ctx);
                    if (value == null) {
                        throw new RuntimeException("key无效");
                    }
                    key = value.toString();
                }
                key = applicationName + "_" + className + "_" + methodName + "_" + key;
                log.info("设置限流锁key={}", key);
                RRateLimiter rateLimiter = this.redisson.getRateLimiter(key);
                if (!rateLimiter.isExists()) {
                    log.info("设置流量,rate={},rateInterval={},rateIntervalUnit={}", rate, rateInterval, rateIntervalUnit);
                    rateLimiter.trySetRate(RateType.OVERALL, rate, rateInterval, rateIntervalUnit);
                    //设置一个过期时间,避免key一直存在浪费内存,这里设置为延长5分钟
                    long millis = rateIntervalUnit.toMillis(rateInterval);
                    this.redisson.getBucket(key).expire(Long.sum(5 * 1000 * 60, millis), TimeUnit.MILLISECONDS);
                }
                boolean acquire = rateLimiter.tryAcquire(1);
                if (!acquire) {
                    //这里直接抛出了异常  也可以抛出自定义异常,通过全局异常处理器拦截进行一些其他逻辑的处理
                    throw new RuntimeException("请求频率过高,此操作已被限制");
                }
                return joinPoint.proceed();
            }
        }
    d.测试类
        @RestController
        @RequestMapping(value = "/user")
        public class UserController {


            @PostMapping(value = "/testForLogin")
            //以account为锁的key,限制每分钟最多登录5次
            @GlobalRateLimiter(key = "#params.account", rate = 5, rateInterval = 60)
            R<Object> testForLogin(@RequestBody @Validated LoginParams params) {
                //登录逻辑
                return R.success("登录成功");
            }
        }

03.自适应限流
    a.介绍
        a.说明
            实现基于 Redisson 的 RRateLimiter,它是一个分布式的限流器,可以在多个服务实例之间共享限流状态
            该实现通过自适应策略来动态调整限流速率,以适应系统负载变化
        b.实现原理
            a.初始化
                AdaptiveRateLimiter 类在初始化时创建一个 RRateLimiter 实例
                并根据提供的 StrategyType 枚举选择一个自适应限流策略
            b.配置恢复
                从 Redis 中恢复限流配置,如果没有现有配置,则设置默认限流速率
            c.自适应策略
                实现了两种策略:基于当前系统负载比例 (LoadProportionStrategy) 和基于历史负载数据 (HistoryBasedStrategy)
                每种策略都有 calculateNewRate 方法,用于根据系统负载计算新的速率
            d.权限获取
                acquirePermission 方法被用来尝试获取一个许可
                在尝试获取许可之前,它会根据当前系统负载调用 adaptRate 方法,该方法使用当前策略来计算并设置新的速率
            e.速率调整
                adaptRate 方法通过比较当前和历史负载数据来决定是否需要调整速率
                如果需要,它会更新 RRateLimiter 的配置,并将新配置持久化到 Redis
        c.优点
            自适应性:能够根据系统负载动态调整限流速率,更好地适应负载变化
            分布式支持:基于 Redisson 的实现允许在多个服务实例间共享限流状态,适用于分布式系统
            灵活性:通过定义策略接口和枚举,可以方便地添加或更换不同的自适应策略
            无阻塞:tryAcquire 方法提供了非阻塞的权限获取,适合需要快速响应的场景
        d.缺点
            复杂性:相对于静态限流规则,自适应限流器的逻辑更复杂,需要更多的测试来确保稳定性
            调优难度:需要根据实际的系统负载和业务需求来调整策略参数,这可能需要大量的监控数据和性能分析
            延迟响应:如果系统负载突然变化,自适应限流器可能会有一定的延迟才能调整到合适的速率
            Redis依赖:依赖于 Redis 作为中心化的状态存储,如果 Redis 出现故障,限流器可能无法正常工作
    b.工具类
        a.代码
            import org.redisson.api.RRateLimiter;
            import org.redisson.api.RateIntervalUnit;
            import org.redisson.api.RateType;
            import org.redisson.api.RedissonClient;

            import java.io.Serializable;
            import java.util.LinkedList;
            import java.util.Queue;

            public class AdaptiveRateLimiter {
                private final RedissonClient redissonClient;
                private final RRateLimiter rateLimiter;
                private final String rateConfigKey;
                private final Queue<Double> loadHistory = new LinkedList<>();
                private final RateAdaptationStrategy strategy;

                public enum StrategyType {
                    LOAD_PROPORTION, // 根据系统负载比例调整
                    HISTORY_BASED    // 根据系统当前负载和历史负载数据调整
                }

                public AdaptiveRateLimiter(RedissonClient redissonClient, String limiterName, String rateConfigKey, StrategyType strategyType) {
                    this.redissonClient = redissonClient;
                    this.rateLimiter = redissonClient.getRateLimiter(limiterName);
                    this.rateConfigKey = rateConfigKey;
                    switch (strategyType) {
                        case LOAD_PROPORTION:
                            this.strategy = new LoadProportionStrategy();
                            break;
                        case HISTORY_BASED:
                            this.strategy = new HistoryBasedStrategy();
                            break;
                        default:
                            throw new IllegalArgumentException("Unknown strategy type");
                    }
                    restoreRateConfiguration();
                }

                private void restoreRateConfiguration() {
                    // 尝试从Redis恢复速率配置
                    RateConfig rateConfig = (RateConfig) redissonClient.getMap(rateConfigKey).get("config");
                    if (rateConfig != null) {
                        rateLimiter.setRate(RateType.OVERALL, rateConfig.rate, rateConfig.rateInterval, rateConfig.unit);
                    } else {
                        // 如果Redis中没有配置,则设置默认速率
                        setRate(new RateConfig(10, 1, RateIntervalUnit.SECONDS));
                    }
                }

                public synchronized void setRate(RateConfig newRateConfig) {
                    // 更新限流器配置
                    rateLimiter.setRate(RateType.OVERALL, newRateConfig.rate, newRateConfig.rateInterval, newRateConfig.unit);
                    // 将新配置持久化到Redis
                    redissonClient.getMap(rateConfigKey).put("config", newRateConfig);
                }

                public boolean tryAcquire() {
                    return rateLimiter.tryAcquire();
                }

                // 自适应限流策略接口
                public interface RateAdaptationStrategy {
                    int calculateNewRate(int currentRate, Queue<Double> loadHistory, double currentLoad);
                }

                // 基于系统负载比例的自适应限流策略
                public static class LoadProportionStrategy implements RateAdaptationStrategy {
                    @Override
                    public int calculateNewRate(int currentRate, Queue<Double> loadHistory, double currentLoad) {
                        if (currentLoad > 0.8) {
                            return Math.max(currentRate / 2, 1);
                        } else if (currentLoad < 0.3) {
                            return currentRate * 2;
                        }
                        return currentRate;
                    }
                }

                // 基于历史系统负载数据的自适应限流策略
                public static class HistoryBasedStrategy implements RateAdaptationStrategy {
                    @Override
                    public int calculateNewRate(int currentRate, Queue<Double> loadHistory, double currentLoad) {
                        loadHistory.add(currentLoad);
                        if (loadHistory.size() > 10) {
                            loadHistory.poll(); // 保持历史数据队列大小
                        }
                        double averageLoad = loadHistory.stream().mapToDouble(l -> l).average().orElse(0.0);
                        if (averageLoad > 0.8) {
                            return Math.max(currentRate / 2, 1);
                        } else if (averageLoad < 0.3) {
                            return currentRate * 2;
                        }
                        return currentRate;
                    }
                }

                public boolean acquirePermission(double currentLoad) {
                    // 根据当前系统负载自适应地调整限流速率
                    adaptRate(currentLoad);
                    return tryAcquire();
                }

                private void adaptRate(double currentLoad) {
                    // 使用策略计算新的速率
                    RateConfig currentRateConfig = (RateConfig) redissonClient.getMap(rateConfigKey).get("config");
                    if (currentRateConfig != null) {
                        int newRate = strategy.calculateNewRate(currentRateConfig.rate, loadHistory, currentLoad);
                        if (newRate != currentRateConfig.rate) {
                            setRate(new RateConfig(newRate, currentRateConfig.rateInterval, currentRateConfig.unit));
                        }
                    }
                }

                // 辅助类,用于存储速率配置
                public static class RateConfig implements Serializable {
                    private final int rate;
                    private final long rateInterval;
                    private final RateIntervalUnit unit;

                    public RateConfig(int rate, long rateInterval, RateIntervalUnit unit) {
                        this.rate = rate;
                        this.rateInterval = rateInterval;
                        this.unit = unit;
                    }
                }
            }
        b.流程图
            开始时,我们检查 Redis 中是否存在限流配置
            如果配置存在,我们从 Redis 恢复限流配
            如果配置不存在,我们设置默认的限流配置
            使用所选策略创建 RRateLimiter 实例
            尝试获取权限(许可)
            根据权限是否被授予,执行相应的操作或拒绝/排队操作
            流程结束
        c.时序图
            客户端 (Client) 请求创建 AdaptiveRateLimiter 实例,传入限流器名称 (limiterName) 和策略类型 (strategyType)
            AdaptiveRateLimiter 通过 RedissonClient 获取 RRateLimiter 实例
            RedissonClient 查询 Redis 是否有现有的限流配置 (rateConfigKey)
            如果 Redis 返回限流配置,RedissonClient 会根据这些配置设置限流器的速率
            AdaptiveRateLimiter 根据传入的 strategyType 选择对应的限流策略 (Strategy)
            创建过程完成后,客户端得到创建好的 AdaptiveRateLimiter 实例
        d.循环过程
            客户端尝试获取权限,调用 acquirePermission 方法,并传入当前系统负载 (currentLoad)
            AdaptiveRateLimiter 使用选择的策略来计算新的速率
            策略 (Strategy) 根据当前负载和历史负载数据返回新的速率 (newRate)
            AdaptiveRateLimiter 使用 RedissonClient 设置新的速率
            RedissonClient 更新 Redis 中的配置
            RedissonClient 尝试从 Redis 获取权限
            Redis 返回权限获取结果
            AdaptiveRateLimiter 将权限获取结果返回给客户端
    c.测试类
        a.代码
            import org.redisson.Redisson;
            import org.redisson.api.RedissonClient;
            import org.redisson.config.Config;

            public class AdaptiveRateLimiterTest {

                public static void main(String[] args) {
                    // 初始化 Redisson 客户端
                    Config config = new Config();
                    config.useSingleServer().setAddress("redis://127.0.0.1:6379");
                    RedissonClient redissonClient = Redisson.create(config);

                    // 创建 AdaptiveRateLimiter 实例
                    String limiterName = "myRateLimiter";
                    String rateConfigKey = "rateLimiterConfig";
                    AdaptiveRateLimiter rateLimiter;

                    // 场景1: 系统负载稳定低于30%,预期速率应该增加
                    System.out.println("场景1: 系统负载稳定低于30%");
                    rateLimiter = new AdaptiveRateLimiter(redissonClient, limiterName, rateConfigKey, AdaptiveRateLimiter.StrategyType.LOAD_PROPORTION);
                    simulateLoadAndTest(rateLimiter, 0.2);

                    // 场景2: 系统负载稳定超过80%,预期速率应该降低
                    System.out.println("场景2: 系统负载稳定超过80%");
                    rateLimiter = new AdaptiveRateLimiter(redissonClient, limiterName, rateConfigKey, AdaptiveRateLimiter.StrategyType.LOAD_PROPORTION);
                    simulateLoadAndTest(rateLimiter, 0.9);

                    // 场景3: 系统负载波动,使用基于历史负载的策略
                    System.out.println("场景3: 系统负载波动,使用基于历史负载的策略");
                    rateLimiter = new AdaptiveRateLimiter(redissonClient, limiterName, rateConfigKey, AdaptiveRateLimiter.StrategyType.HISTORY_BASED);
                    simulateFluctuatingLoadAndTest(rateLimiter);

                    // 关闭 Redisson 客户端
                    redissonClient.shutdown();
                }

                /**
                 * 模拟了一个稳定的系统负载,并多次尝试获取权限,以查看限流器的行为是否符合预期。
                 * @param rateLimiter
                 * @param load
                 */
                private static void simulateLoadAndTest(AdaptiveRateLimiter rateLimiter, double load) {
                    for (int i = 0; i < 5; i++) {
                        boolean acquired = rateLimiter.acquirePermission(load);
                        System.out.println("尝试获取权限: " + (acquired ? "成功" : "失败"));
                        // 模拟一段时间后再次尝试
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }

                /**
                 * 模拟了一个波动的系统负载,同样多次尝试获取权限
                 * @param rateLimiter
                 */
                private static void simulateFluctuatingLoadAndTest(AdaptiveRateLimiter rateLimiter) {
                    double[] loads = {0.5, 0.8, 0.3, 0.7, 0.2};
                    for (double load : loads) {
                        boolean acquired = rateLimiter.acquirePermission(load);
                        System.out.println("当前负载: " + load + ",尝试获取权限: " + (acquired ? "成功" : "失败"));
                        // 模拟一段时间后再次尝试
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        b.说明
            首先设置了 Redisson 客户端并创建了 AdaptiveRateLimiter 的实例。然后,我们为不同的场景定义了测试用例:
            场景1: 测试系统负载稳定低于30%时的行为
            场景2: 测试系统负载稳定超过80%时的行为
            场景3: 测试基于历史负载的策略在系统负载波动时的行为
        c.说明
            simulateLoadAndTest 方法模拟了一个稳定的系统负载,并多次尝试获取权限,以查看限流器的行为是否符合预期
            simulateFluctuatingLoadAndTest 方法模拟了一个波动的系统负载,同样多次尝试获取权限

1.34 [5]Redisson锁:大key方案

01.处理大key的策略
    a.分页加载
        不要一次性加载整个大 key 的所有元素,而是使用 LRANGE 命令分页加载,每次只获取部分元素
    b.分割key
        可以将一个大的 List 分割成多个小的 List
        例如根据数据的时间戳、ID 范围或其他逻辑进行分割,每个小 List 作为一个独立的 key 存储
    c.数据压缩
        如果数据是可以压缩的,可以在客户端对数据进行压缩后再存储到 Redis,这样可以减少内存的使用量
    d.数据存储策略优化
        评估是否所有数据都需要存储在 Redis 中,或者是否可以通过其他方式(如数据库)来存储部分数据
    e.使用其他数据结构
        如果适用,可以考虑使用其他更内存高效的数据结构,比如 zset(有序集合)
    f.定期清理
        定期检查并清理不再需要的数据,以释放内存
    g.Redis集群
        如果单个 Redis 实例的内存不足以处理大量数据,可以考虑使用 Redis 集群来分散数据和负载
    h.监控和警报
        使用 Redis 的 INFO MEMORY 命令或其他监控工具来监控内存使用情况,并设置警报机制

02.Redission有效处理大量数据的加载和缓存,同时确保数据的一致性和高效的缓存管理
    a.分布式环境下的数据一致性
        使用分布式锁(RLock)来确保在更新缓存时,只有一个进程/线程可以操作缓存,从而避免并发写入导致的数据不一致问题
        使用缓存标记(cacheMarker)来指示缓存的状态,确保在更新数据时,可以先使缓存无效,然后再进行缓存更新操作
    b.缓存失效和更新策略
        设置缓存的过期时间(TTL),确保缓存数据不会永久存储,从而避免潜在的陈旧数据问题
        在更新缓存数据之前,先将缓存标记设置为无效,更新完成后再重新将其设置为有效,并更新过期时间,这样可以减少读取陈旧缓存数据的风险
    c.处理大规模数据集
        由于单个 Redis 值可能会非常大,设计中采用了分批存储数据的方法。这样可以避免单个 Redis 值的大小超过处理能力或导致性能问题
        通过使用子列表(sublist)的方式,将大数据集分解为更小的批次,并为每个批次生成单独的缓存键
    d.优化读取性能
        在读取数据时,先检查缓存是否存在数据。如果存在,则直接从缓存中读取,避免了不必要的数据库访问,从而提高了性能
        如果缓存中没有数据,则从数据库加载数据,并将结果存储到缓存中,以便后续请求可以快速获取
    e.键名生成
        为了确保缓存的唯一性和可查询性,设计了一个方法(generateKey)来根据输入参数生成一个唯一的缓存键。这保证了相同的查询参数将会引用相同的缓存数据

03.实现代码
    a.基础环境
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.stereotype.Service;
        import lombok.extern.slf4j.Slf4j;

        import java.util.List;
        import java.util.concurrent.TimeUnit;

        @Service
        @Slf4j
        public class LargeDataStorageService {

            @Autowired
            private RedissonClient redissonClient;

            @Autowired
            private ShelfService shelfService;

            private final long ttl = 1; // 缓存过期时间
            private final TimeUnit timeUnit = TimeUnit.MINUTES;

            /**
             * 生成key
             *
             * @param c 参数1
             * @param w 参数2
             * @param l 参数3
             * @return 生成的唯一key
             */
            private String generateKey(List<String> c, List<Integer> w, List<Integer> l) {
                // Implement a method to generate a unique key based on the method parameters
                // This could be a concatenation of the parameters' hashCodes or a more complex logic
                return "shelfCodes:" + c.hashCode() + ":" + w.hashCode() + ":" + l.hashCode();
            }

            // 其他业务方法
        }
    b.生成Key
        /**
         * 生成key
         *
         * @param c
         * @param w
         * @param l
         * @return
         */
        private String generateKey(List<String> c, List<Integer> w, List<Integer> l) {
            // Implement a method to generate a unique key based on the method parameters
            // This could be a concatenation of the parameters' hashCodes or a more complex logic
            return "shelfCodes:" + c.hashCode() + ":" + w.hashCode() + ":" + l.hashCode();
        }
    c.简单版
        public List<String> loadSv(List<String> c, List<Integer> w, List<Integer> l) throws ExecutionException, InterruptedException {
            String key = generateKey(c, w, l);
            List<String> shelfCodes = new ArrayList<>();
            boolean dataFoundInRedis = false;
            // 尝试从 Redis 分批获取数据
            int index = 0;
            while (true) {
                String sublistKey = key + ":sublist:" + index;
                RList<String> sublist = redissonClient.getList(sublistKey, StringCodec.INSTANCE);
                if (sublist.isExists()) {
                    shelfCodes.addAll(sublist.readAll());
                    dataFoundInRedis = true;
                    index++;
                } else {
                    break;
                }
            }
            // 如果 Redis 中没有数据,则从数据库加载并存储到 Redis
            if (!dataFoundInRedis) {
                shelfCodes = shelfService.getShelfCodes(c, w, l);
                storeS(c, w, l, shelfCodes, ttl, timeUnit); // 设置过期时间
            }
            return shelfCodes;
        }
    d.加锁版
        public List<String> loadS(List<String> c, List<Integer> w, List<Integer> l) {
            String key = generateKey(c, w, l);
            List<String> shelfCodes = new ArrayList<>();
            boolean dataFoundInRedis = false;

            // 获取缓存标记
            RBucket<Boolean> cacheMarker = redissonClient.getBucket(key + ":marker", StringCodec.INSTANCE);
            if (Boolean.TRUE.equals(cacheMarker.get())) {
                // 尝试从 Redis 分批获取数据
                int index = 0;
                while (true) {
                    String sublistKey = key + ":sublist:" + index;
                    RList<String> sublist = redissonClient.getList(sublistKey, StringCodec.INSTANCE);
                    if (!sublist.isEmpty()) {
                        shelfCodes.addAll(sublist);
                        dataFoundInRedis = true;
                        index++;
                    } else {
                        break;
                    }
                }
            }

            // 如果 Redis 中没有数据,则从数据库加载并存储到 Redis
            if (!dataFoundInRedis) {
                // 使用分布式锁确保一致性
                RLock lock = redissonClient.getLock(key + ":lock");
                lock.lock();
                try {
                    // 双重检查,确保数据没有被其他线程加载
                    if (Boolean.TRUE.equals(cacheMarker.get())) {
                        shelfCodes.clear();
                        int index = 0;
                        while (true) {
                            String sublistKey = key + ":sublist:" + index;
                            RList<String> sublist = redissonClient.getList(sublistKey, StringCodec.INSTANCE);
                            if (!sublist.isEmpty()) {
                                shelfCodes.addAll(sublist);
                                index++;
                            } else {
                                break;
                            }
                        }
                    } else {
                        shelfCodes = shelfService.getShelfCodes(c,w,l);
                        // 先使缓存标记无效
                        cacheMarker.set(false);
                        // 存储新的数据到缓存
                        storeS(c,w,l, shelfCodes, ttl, timeUnit);
                        // 重新标记缓存为有效
                        cacheMarker.set(true, ttl, timeUnit);
                    }
                } finally {
                    lock.unlock();
                }
            }

            return shelfCodes;
        }
        -----------------------------------------------------------------------------------------------------
        1.客户端 (Client) 请求 LargeDataStorageService 加载货架代码 (loadShelf)
        2.服务首先检查 Redis 中的缓存标记 (cacheMarker)
        3.如果缓存标记为真,表示缓存中有数据,服务将循环获取缓存中的所有子列表,直到没有更多子列表
        4.如果缓存标记不存在或为假,服务将尝试获取 Redis 的分布式锁
        5.在锁内部,服务再次检查缓存标记,以确保在等待锁的期间没有其他进程已经加载了数据并更新了缓存
        6.如果缓存标记仍然为假,服务将从数据库 (ShelfService) 加载货架代码
        7.服务将数据库中获取的货架代码存储到 Redis 缓存中,并将缓存标记设置为真
        8.服务释放 Redis 的分布式锁
        9.服务返回货架代码给客户端
    e.分片存储
        /**
         * 分片存储
         *
         * @param c
         * @param w
         * @param l
         * @param s
         * @param ttl
         * @param timeUnit
         */
        public void storeS(List<String> c, List<Integer> w, List<Integer> l, List<String> s, long ttl, TimeUnit timeUnit) {
            // Generate a unique key for the given parameters
            String key = generateKey(c, w, l);
            // Batch operation for atomic execution
            RBatch batch = redissonClient.createBatch();
            // Assuming each sublist has a reasonable size that Redis can handle efficiently
            int sublistSize = 1000; // Adjust this size as needed
            for (int i = 0; i < s.size(); i += sublistSize) {
                int end = Math.min(s.size(), i + sublistSize);
                List<String> sublist = s.subList(i, end);
                // Store each sublist in a separate key with a suffix
                String sublistKey = key + ":sublist:" + (i / sublistSize);
                RListAsync<String> listAsync = batch.getList(sublistKey, StringCodec.INSTANCE);
                listAsync.addAllAsync(sublist);
                listAsync.expireAsync(ttl, timeUnit);
            }
            // Execute batch commands in one round trip
            batch.execute();
        }
    f.说明
        a.loadS 方法
            接受c(c)、w (w)和 l(l)作为参数
            使用这些参数生成一个唯一的缓存键
            尝试从 Redis 缓存中分批获取数据,如果找到数据则返回
            如果缓存中没有数据,使用分布式锁来防止多个进程同时加载和缓存数据
            在锁内部,再次检查缓存标记以确认数据是否已被其他进程缓存
            如果数据仍未被缓存,则调用 shelfService.getShelfCodes 方法从数据库中加载数据
            使用 storeShelfCodes 方法将数据存储到 Redis,并设置缓存标记,标记数据现在可用
            最后释放锁并返回加载的数据
        b.storeS 方法
            接受相同的参数以及要存储的货架代码列表和过期时间设置
            使用批处理操作将数据分批存储到 Redis 中,每个批次是一个子列表,这有助于避免单个 Redis 值过大
            设置每个子列表的过期时间
        c.generateKey方法
            生成基于输入参数的唯一键,用于在 Redis 中标识特定的数据集
    g.这个类的实现具有以下特点
        a.数据一致性
            通过使用分布式锁和缓存标记来确保数据一致性,防止缓存污染
        b.缓存失效策略
            通过设置过期时间来确保缓存数据最终会被刷新,允许新的数据被加载和缓存
        c.性能优化
            通过分批处理大量数据来优化性能,避免单个大型 Redis 值可能导致的性能问题

1.35 [5]Redisson锁:批量同步异步

01.说明
    简单的 Redisson Java 工具类示例,它包含了批量操作和异步操作的方法
    这个类使用了 Redisson 的 RBucket 和 RBatch 接口来执行同步和异步的批量操作

02.工具类
    a.代码
        import com.google.common.collect.Lists;
        import org.redisson.Redisson;
        import org.redisson.api.*;
        import org.redisson.config.Config;
        import java.util.ArrayList;
        import java.util.HashMap;
        import java.util.List;
        import java.util.Map;
        import java.util.concurrent.CompletionStage;
        import java.util.concurrent.ExecutionException;

        public class RedissonUtils {

            private final RedissonClient redissonClient;

            public RedissonUtils(String redisUrl) {
                Config config = new Config();
                config.useSingleServer().setAddress(redisUrl);
                this.redissonClient = Redisson.create(config);
            }

            // 同步批量设置键值对
            public void setBulk(Map<String, Object> keyValueMap) {
                RBatch batch = redissonClient.createBatch();
                keyValueMap.forEach((key, value) ->
                        batch.getBucket(key).setAsync(value)
                );
                batch.execute();
            }

            // 异步批量获取键值对
            public CompletionStage<Map<String, Object>> getBulkAsync(Iterable<String> keys) {
                RBatch batch = redissonClient.createBatch();
                keys.forEach(key ->
                        batch.getBucket(key).getAsync()
                );
                return batch.executeAsync().thenApply(responses -> {
                    // 处理结果
                    Map<String, Object> results = new HashMap<>();
                    int i = 0;
                    for (String key : keys) {
                        results.put(key, responses.getResponses().get(i));
                        i++;
                    }
                    return results;
                });
            }

            // 异步设置单个键值对
            public CompletionStage<Void> setAsync(String key, Object value) {
                RBucket<Object> bucket = redissonClient.getBucket(key);
                return bucket.setAsync(value).thenAccept(result -> {
                        System.out.println("Key " + key + " set successfully");
                });
            }

            // 异步获取单个键值
            public RFuture getAsync(String key) throws ExecutionException, InterruptedException {
                RBucket<Object> bucket = redissonClient.getBucket(key);
                return bucket.getAsync();
            }


            public <V> List<V> getObject(List<String> keyList) {
                List<V> results = new ArrayList<>(keyList.size());
                List<List<String>> spilts = Lists.partition(keyList, 1000);
                spilts.forEach(
                        singleKeyList -> {
                            RBatch batch = redissonClient.createBatch();
                            singleKeyList.forEach(
                                    key -> batch.getBucket(key).getAsync()
                            );
                            BatchResult baseBatchResult = batch.execute();
                            results.addAll(baseBatchResult.getResponses());
                        }
                );
                return results;
            }


            public <V> List<V> getMap(List<String> keyList, String fieldKey) {
                List<V> results = new ArrayList<>(keyList.size());
                List<List<String>> spilts = Lists.partition(keyList, 1000);
                spilts.forEach(
                        singleKeyList -> {
                            RBatch batch = redissonClient.createBatch();
                            singleKeyList.forEach(
                                    key -> batch.getMap(key).getAsync(fieldKey)
                            );
                            BatchResult baseBatchResult = batch.execute();
                            results.addAll(baseBatchResult.getResponses());
                        }
                );
                return results;
            }

            // 关闭 Redisson 客户端
            public void shutdown() {
                redissonClient.shutdown();
            }
        }
    b.说明
        在这个工具类中,我们实现了以下方法:
        setBulk: 批量同步设置多个键值对
        getBulkAsync: 异步批量获取多个键的值,并返回一个 CompletionStage 对象
        setAsync: 异步设置单个键值对,并在操作完成后打印一条消息
        getAsync: 异步获取单个键的值,并返回一个 CompletionStage 对象
        shutdown: 用于关闭 Redisson 客户端

03.测试类
    import com.google.common.collect.Lists;
    import org.redisson.api.RFuture;
    import org.redisson.api.RedissonClient;

    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.concurrent.CompletionStage;
    import java.util.concurrent.ExecutionException;

    public class RedissonManagerExample {

        private RedissonClient redissonClient; // 假设这是您的Redisson客户端实例

        public RedissonManagerExample(RedissonClient redissonClient) {
            this.redissonClient = redissonClient;
        }

    // 假设这些是您的RedissonManager类中的方法
    // ...(上面提供的方法)

        public static void main(String[] args) throws ExecutionException, InterruptedException {
            // 创建 RedissonManager 实例
            RedissonUtils redissonManager = new RedissonUtils("IP"); // 使用您的redissonClient实例

            // 同步批量设置键值对
            Map<String, Object> keyValueMap = new HashMap<>();
            keyValueMap.put("key1", "value1");
            keyValueMap.put("key2", "value2");
            redissonManager.setBulk(keyValueMap);

            // 异步批量获取键值对
            Iterable<String> keys = keyValueMap.keySet();
            CompletionStage<Map<String, Object>> getBulkStage = redissonManager.getBulkAsync(keys);

            // 处理异步批量获取结果
            getBulkStage.thenAccept(results -> {
                results.forEach((key, value) -> System.out.println("Key: " + key + ", Value: " + value));
            });

            // 异步设置单个键值对
            String newKey = "key3";
            String newValue = "value3";
            CompletionStage<Void> setAsyncStage = redissonManager.setAsync(newKey, newValue);

            // 处理异步设置单个键值对的结果
            setAsyncStage.thenRun(() -> System.out.println("Async set operation completed for key: " + newKey));

            // 等待异步操作完成以避免程序过早退出
            // 注意:在实际应用中,您可能会有更复杂的流程来管理程序的生命周期
            Thread.sleep(1000); // 简单的延时等待异步操作完成


            // 异步获取单个键值
            String key = "sampleKey";
            RFuture<Object> future = redissonManager.getAsync(key);
            future.whenComplete((result, exception) -> {
                if (exception != null) {
                    System.err.println("An error occurred: " + exception.getMessage());
                } else {
                    System.out.println("Fetched value: " + result);
                }
            });


            // 同步批量获取多个键的值
            List<String> key1 = Lists.newArrayList("key1", "key2", "key3");
            List<Object> values = redissonManager.getObject(key1);
            System.out.println("Fetched values: " + values);

            // 同步批量获取多个键对应的 Map 中特定字段的值
            String fieldKey = "field1";
            List<Object> fieldValues = redissonManager.getMap(keys, fieldKey);
            System.out.println("Fetched field values: " + fieldValues);

            // 关闭 Redisson 客户端
            redissonManager.shutdown();
        }
    }

1.36 [5]Redisson锁:静态获取释放锁

01.说明
    a.加锁原理
        a.描述
            Redisson 的锁实现通常使用 Redis 的 SETNX 命令或 SET 命令的变体(具有 NX 标志)
            这些命令只有在键不存在时才会设置键值对
        b.过程
            加锁时,Redisson 将生成一个随机的 UUID(作为锁的值),然后尝试使用 SETNX 或 SET 命令将其存储在 Redis 中
            如果命令执行成功,那么这个线程就成功地获取了锁
            如果键已经存在,表示锁已经被其他线程持有,当前线程将进入等待状态,直到锁被释放或超时
        c.防止死锁
            Redisson 锁还会设置一个过期时间,这个时间被称为锁的“租约时间”
            如果持有锁的线程在这段时间内没有释放锁,锁将自动被释放
    b.解锁原理
        a.描述
            解锁时,Redisson 会发送一个 Lua 脚本到 Redis
            这个脚本会检查当前锁是否由当前线程持有(通过比较 UUID),如果是,则删除锁(删除键)
        b.特点
            这个操作是原子的,以确保锁的安全释放
    c.优点
        a.易用性
            Redisson 提供了丰富的接口和简单的使用方式,使得在分布式环境中实现锁和同步变得容易
        b.性能
            Redis 本身具有高性能,Redisson 的实现确保了在分布式锁的使用中也能保持高性能
        c.高可用性和可伸缩性
            Redisson 可以通过 Redis 集群来提供高可用性和可伸缩性
        d.多种锁的实现
            Redisson 不仅仅支持可重入锁,还支持公平锁、联锁、红锁等多种锁的实现
    d.缺点
        a.依赖于 Redis
            如果 Redis 服务不可用,那么基于 Redisson 的锁也会不可用
        b.网络延迟
            锁操作依赖于网络交互,如果 Redis 服务器与应用服务器之间的网络延迟较大,可能会影响锁的获取和释放
        c.资源消耗
            如果大量使用分布式锁,可能会增加 Redis 的负载和网络流量
        d.时钟同步问题
            分布式系统中的时钟同步问题可能会影响锁的正确性,尤其是在锁的续租和过期检测方面

02.代码
    a.说明
        一个简单的RedissonLockUtil类的示例,提供了静态方法来获取和释放锁
    b.工具类
        import org.redisson.Redisson;
        import org.redisson.api.RLock;
        import org.redisson.api.RedissonClient;
        import org.redisson.config.Config;

        import java.util.concurrent.TimeUnit;

        public class RedissonLockUtil {

            private static RedissonClient redissonClient;

            static {
                // 初始化 Redisson 客户端
                Config config = new Config();
                config.useSingleServer().setAddress("redis://127.0.0.1:6379");
                // 根据 Config 创建 RedissonClient 实例
                redissonClient = Redisson.create(config);
            }

            // 获取锁
            public static boolean lock(String lockKey, long leaseTime, TimeUnit timeUnit) {
                RLock lock = redissonClient.getLock(lockKey);
                try {
                    return lock.tryLock(0, leaseTime, timeUnit);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return false;
                }
            }

            // 释放锁
            public static void unlock(String lockKey) {
                RLock lock = redissonClient.getLock(lockKey);
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }

            // 关闭 Redisson 客户端
            public static void shutdown() {
                if (redissonClient != null) {
                    redissonClient.shutdown();
                }
            }
        }
    c.测试类
        public class BusinessService {

            // 业务逻辑方法
            public void doSomething() {
                String lockKey = "myLockKey";
                boolean isLocked = RedissonLockUtil.lock(lockKey, 5, TimeUnit.SECONDS);
                if (isLocked) {
                    try {
                        // 执行业务逻辑
                        System.out.println("执行业务逻辑...");
                    } finally {
                        // 释放锁
                        RedissonLockUtil.unlock(lockKey);
                    }
                } else {
                    System.out.println("获取锁失败,业务逻辑未执行");
                }
            }
        }
    d.说明
        在这个例子中,RedissonLockUtil 类提供了 lock 和 unlock 方法,分别用于获取和释放锁
        lock 方法尝试立即获取锁,并设置锁的持有时间。如果获取锁成功,则返回 true
        如果获取失败,则返回 false。unlock 方法检查当前线程是否持有锁,如果是,则释放锁
        -----------------------------------------------------------------------------------------------------
        在业务服务中,我们调用 RedissonLockUtil.lock 来尝试获取锁,并在成功获取锁之后执行业务逻辑
        不管业务逻辑是否成功,最后都会在 finally 块中调用 RedissonLockUtil.unlock 来释放锁
        -----------------------------------------------------------------------------------------------------
        请注意,这个工具类假设你使用的是单个 Redis 服务器
        如果你的环境中使用的是 Redis 集群或哨兵模式
        你需要根据你的配置调整 Config 对象的初始化代码
        -----------------------------------------------------------------------------------------------------
        此外,不要忘记在应用程序关闭时调用 RedissonLockUtil.shutdown 方法来优雅地关闭 Redisson 客户端

1.37 [5]Redisson锁:可重入锁

00.可重入加锁机制
    a.定义
        可重入锁允许同一个线程多次获取同一个锁,而不会导致死锁
    b.原理
        Redisson通过在Redis中存储线程标识和重入计数来实现可重入锁

01.依赖
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson</artifactId>
        <version>3.16.2</version>
    </dependency>

02.自定义配置类
    package com.example.demo.config;

    import org.redisson.Redisson;
    import org.redisson.api.RedissonClient;
    import org.redisson.config.Config;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    import java.io.IOException;

    @Configuration
    public class RedissonConfig {
        /**
         * 对 Redisson 的使用都是通过 RedissonClient 对象
         * @return
         * @throws IOException
         */
        @Bean(destroyMethod="shutdown") // 服务停止后调用 shutdown 方法。
        public RedissonClient redisson() throws IOException {
            // 创建配置
            Config config = new Config();
            config.useSingleServer()
                    .setAddress("redis://xx.xx.x.x:6379")
                    .setPassword("123456");
            // 集群模式
            // config.useClusterServers().addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001");
            return Redisson.create(config);
        }
    }

03.测试API
    package com.example.demo.controller;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    @RestController
    @RequestMapping("/api/redis")
    public class MyController {

        @Autowired
        private ReentrantLockService reentrantLockService;

        @GetMapping("/locked-operation")
        public String performLockedOperation() {
            // 模拟多个线程同时调用可重入锁的操作
            for (int i = 1; i <= 5; i++) {
                final int threadNumber = i;
                new Thread(() -> {
                    reentrantLockService.performLockedOperation();
                }).start();
            }
            return "Locked operation initiated.";
        }
    }

04.测试类
    package com.example.demo.controller;

    import lombok.extern.slf4j.Slf4j;
    import org.redisson.api.RLock;
    import org.redisson.api.RedissonClient;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;

    import java.util.concurrent.TimeUnit;

    @Service
    @Slf4j
    public class ReentrantLockService {

        @Autowired
        private RedissonClient redissonClient;

        public void performLockedOperation() {
            // 获取可重入锁
            RLock lock = redissonClient.getLock("myReentrantLock");

            String threadName = Thread.currentThread().getName();
            try {
                // 尝试加锁,最多等待10秒,锁的自动释放时间为30秒
                boolean isLocked = lock.tryLock(10, 30, TimeUnit.SECONDS);
                if (isLocked) {
                    // 执行需要加锁的操作
                    log.info(threadName + " - 获取锁成功,执行加锁操作...");

                    // 模拟业务操作
                    Thread.sleep(5000);

                    log.info(threadName + " - 加锁操作完成。");
                } else {
                    log.info(threadName + "在指定时间内无法获取锁。");
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                System.err.println(threadName + "尝试获取锁时发生中断。");
            } finally {
                // 释放锁
                if (lock.isHeldByCurrentThread()) {
                    log.info(threadName + " - 释放锁");
                    lock.unlock();
                }
            }
        }
    }

05.输出日志
    2023-11-17 15:56:13.935  INFO 12316 --- [      Thread-17] c.e.d.controller.ReentrantLockService    : Thread-17 - 获取锁成功,执行加锁操作...
    2023-11-17 15:56:18.945  INFO 12316 --- [      Thread-17] c.e.d.controller.ReentrantLockService    : Thread-17 - 加锁操作完成。
    2023-11-17 15:56:18.969  INFO 12316 --- [      Thread-17] c.e.d.controller.ReentrantLockService    : Thread-17 - 释放锁
    2023-11-17 15:56:19.020  INFO 12316 --- [      Thread-16] c.e.d.controller.ReentrantLockService    : Thread-16 - 获取锁成功,执行加锁操作...
    2023-11-17 15:56:23.901  INFO 12316 --- [      Thread-19] c.e.d.controller.ReentrantLockService    : Thread-19在指定时间内无法获取锁。
    2023-11-17 15:56:23.901  INFO 12316 --- [      Thread-20] c.e.d.controller.ReentrantLockService    : Thread-20在指定时间内无法获取锁。
    2023-11-17 15:56:23.909  INFO 12316 --- [      Thread-18] c.e.d.controller.ReentrantLockService    : Thread-18在指定时间内无法获取锁。
    2023-11-17 15:56:24.023  INFO 12316 --- [      Thread-16] c.e.d.controller.ReentrantLockService    : Thread-16 - 加锁操作完成。
    2023-11-17 15:56:24.046  INFO 12316 --- [      Thread-16] c.e.d.controller.ReentrantLockService    : Thread-16 - 释放锁

06.分析日志
    a.获取锁(Thread-17)
        Thread-17 成功获取了名为 "myReentrantLock" 的可重入锁
        执行了一段需要锁保护的业务操作,模拟了一个长时间的操作,持有锁
    b.释放锁(Thread-17)
        markdown 代码解读复制代码- `Thread-17` 完成了业务操作,释放了锁
    c.获取锁(Thread-16)
        Thread-16 在 Thread-17 释放锁之后成功获取了相同的锁
        执行了一段需要锁保护的业务操作,然后释放了锁
    d.获取锁失败(Thread-19, Thread-20, Thread-18)
        Thread-19, Thread-20, 和 Thread-18 在指定的等待时间内无法获取锁,因为此时 Thread-16 持有锁
    e.总结原理
        通过redissonClient.getLock("myReentrantLock")创建了一个可重入锁对象
        lock.tryLock(10, 30, TimeUnit.SECONDS)尝试获取锁,在10秒内等待,锁的自动释放时间为30秒
        如果获取锁成功,执行需要加锁的操作,然后释放锁
        其他线程在获取锁时,如果超过指定时间未能成功获取,会得到相应的提示

1.38 [5]Redisson锁:阻塞与非阻塞

01.阻塞方式
    a.定义
        在阻塞方式中,线程在尝试获取锁时,如果锁已被其他线程占用,那么当前线程会被阻塞
        一直等到锁被释放后才能继续执行。在阻塞模式下,线程可能会等待相当长的时间,直到获取到锁
    b.代码
        ReentrantLock lock = new ReentrantLock();
        // 阻塞方式获取锁
        lock.lock();
        try {
            // 执行需要锁保护的代码
        } finally {
            lock.unlock();
        }

02.非阻塞方式
    a.定义
        在非阻塞方式中,线程尝试获取锁时
        如果锁已被其他线程占用,当前线程不会被阻塞,而是立即返回一个结果
        告知是否成功获取锁。非阻塞方式下,线程不会等待,而是可以继续执行其他操作
    b.代码
        ReentrantLock lock = new ReentrantLock();
        // 非阻塞方式尝试获取锁
        if (lock.tryLock()) {
            try {
                // 执行需要锁保护的代码
            } finally {
                lock.unlock();
            }
        } else {
            // 未获取到锁的处理逻辑
        }

1.39 [5]Redisson锁:异步任务加锁

00.示例
    a.需求
        如果当前用户正在执行异步任务时,调接口无法执行,抛出异常,等待其执行结束可继续创建
    b.场景
        由于异步任务抛出异常,主线程无法感知,所以这里在 异步任务 外创建锁
    c.采用redisson的分布式锁实现
        //执行异步任务接口
        @PostMapping("...")
         void autoCreateUser(@RequestBody AutoCreateUserDTO req) throws InterruptedException {
            Long userId = SecurityUtils.getUserId();
            RLock lock = redissonClient.getFairLock(String.format(RedisConstants.ASYNC_TASK, userId));
            log.info("创建任务接口,线程Id:{}", Thread.currentThread().getId());
            if (lock.tryLock(0L, 60L, TimeUnit.SECONDS)) {//不等待立即返回加锁成功与否,持有锁60秒自动释放
                CompletableFuture.runAsync(() -> {
                    log.info("异步任务,线程Id:{}", Thread.currentThread().getId());
                    try {
                        userService.autoCreateUser(req);
                    } catch (Exception e) {
                     if(lock.isHeldByCurrentThread()){
                        log.info("释放锁,线程Id:{}",Thread.currentThread().getId());
                        lock.unlock();
                      }
                    }
                }).exceptionally(e -> {
                    log.error("异步任务执行失败", e);
                    return null;
                });
            } else {
                throw new RException(ResCode.REPEATED_REQUESTS_FROM_USERS, "正在执行创建任务,请稍后再试");
            }
        }

01.第一坑:@Async注解失效
    a.描述
        如果不用CompletableFuture.runAsync() 而是用注解@Async标记异步任务时注意不要放在同一个类
        同一个类中的一个方法调用另一个标记了@Async的方法,会导致注解失效
    b.原因
        Spring在扫描@Async注解时,会动态生成一个子类代理类,代理类会继承自原来的bean
        该代理类会代为执行异步逻辑,如果@Async标记的方法被同一个类中的其他方法调用
        该方法不会走代理类而是直接调用原来的bean,从而导致注解失效

02.第二坑:异步任务无法获取应用上下文信息
    a.描述
        异步任务无法获取应用上下文信息,如需使用,要在参数里进行传递
    b.示例
        Long userId = SecurityUtils.getUserId(); //从jwt解析用户id
        userService.autoCreateUser(req,userId);

03.第三坑:可重入锁被同一个线程调用时,tryLock会成功
    a.描述
        刚开始使用isLocked()时一直返回false,只要键在redis中存在,就返回false
        使用tryLock获取锁后,再解锁这种笨办法;由于是可重入锁,同一个线程调用tryLock,就算没unlock,也会tryLock成功
    b.示例
        @PostMapping("...")
        Integer autoCreateUserStatus() {
            Long userId = SecurityUtils.getUserId();
            RLock lock = redissonClient.getLock(String.format(RedisConstants.ASYNC_TASK, userId));
            if (lock.tryLock()) {
                try {
                    log.info("获取锁后线程Id:{}",Thread.currentThread().getId());
                    return 1;
                } finally {
                    lock.unlock();
                }
            } else {
                return 0;
            }
        }
        -----------------------------------------------------------------------------------------------------
        这样写,一直无法满足需求,锁状态飘忽不定,莫名被锁,莫名又释放了。
        通过输出线程id排查,发现各阶段输出的id值都不同。

04.第四坑:异步任务runAsync 和 whenComplete方法都会开辟新线程执行任务
    a.描述
        这里获取到的线程id不同,导致isHeldByCurrentThread()方法一直返回false
        该方法判断锁是否被当前线程持有
    b.示例
        if(lock.isHeldByCurrentThread()){
           log.info("释放锁,线程Id:{}",Thread.currentThread().getId());
           lock.unlock();
        }
        -----------------------------------------------------------------------------------------------------
        如果将判断去掉,则会报错:
        java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node
        解释:锁不被该线程持有,既runAsync、whenComplete方法都会使用全新的线程而非主线程执行任务
        主线程创建的锁,不能被其他线程释放

05.第五坑:锁是在主线程创建的,异步任务执行完之后,如何在主线程中释放锁呢?
    a.解决方案
        最后发现 lock.unlockAsync() 可以传入线程id
    b.最终解决方案
        将接口线程id传递给异步任务的回调方法whenComplete(这个方法会在runAsync执行完成后回调)
        在这里执行lock.unlockAsync(接口线程id),在接口线程中将锁释放,完美实现需求
    c.示例
        @PostMapping("...")
        void autoCreateUser(@RequestBody AutoCreateUserDTO req) throws InterruptedException {
            Long userId = SecurityUtils.getUserId();
            RLock lock = redissonClient.getFairLock(String.format(RedisConstants.ASYNC_TASK, userId));
            log.info("创建任务接口,线程Id:{}", Thread.currentThread().getId());
            long thisId = Thread.currentThread().getId();
            if (lock.tryLock(0L, 90L, TimeUnit.SECONDS)) {
                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                    log.info("异步任务,线程Id:{}", Thread.currentThread().getId());
                    userService.autoCreateUser(req);
                }).exceptionally(e -> {
                    log.error("异步任务执行失败", e);
                    return null;
                });
                // 异步任务线程id不同,需在主线程中释放锁
                future.whenComplete((result, ex) -> {
                    try {
                        log.info("释放锁,线程Id:{}", thisId);
                        lock.unlockAsync(thisId);
                    } catch (Exception e) {
                        log.error("释放锁失败", e);
                    }
                });
            } else {
                throw new RException(ResCode.REPEATED_REQUESTS_FROM_USERS, "正在执行创建任务,请稍后再试");
            }
        }
        //获取锁状态
        @PostMapping("...")
        Integer autoCreateUserStatus() {
            Long userId = SecurityUtils.getUserId();
            RLock lock = redissonClient.getLock(String.format(RedisConstants.ASYNC_TASK, userId));
            log.info("获取锁状态,线程Id:{}", Thread.currentThread().getId());
            return lock.isLocked() ? 0 : 1; //isLocked()获取锁状态,这个方法很好很单纯,一开始错怪它了,对不起
        }

1.40 [5]Redisson锁:7条实战经验

01.为啥不用 RedisTemplate 来搞锁?
    a.你是不是也干过这种事儿:
        Boolean success = redisTemplate.opsForValue().setIfAbsent("lockKey", "anyVal", 10, TimeUnit.SECONDS);
        再加上 delete("lockKey") 释放锁,看上去人畜无害、灵活轻便。
    b.但它有三个致命问题:
        a.问题 1:线程挂了,锁永远不释放?
            忘记加过期时间,锁变成僵尸锁,谁都抢不走,业务直接卡死。
        b.问题 2:释放锁误删别人的锁!
            线程 A 加锁,过期前刚好线程 B 重复拿到锁,
            结果线程 A 还在 finally 里执行了 delete("lockKey") ——
            恭喜你,B 的锁被误删,相当于别人家刚换门锁,你拿着老钥匙又打开了!
        c.问题 3:没有重入性、没有阻塞等待、没有续期机制!
            你要想支持:
                自动续期(像看门狗那样)
                阻塞等待一会再试 
                公平锁 
                锁续命 
                可重入锁 
                tryLock 等待+租期 
            基本上就是个“山寨锁”,功能少得可怜。
        d.问题 4:不支持 RedLock / 多节点一致性
            你用 RedisTemplate 时,是不具备多 Redis 节点下的锁一致性方案的(比如云 Redis 异地多活、主从切换等),一出故障就“锁飞了”。
    c.用 Redisson 的理由就呼之欲出了!
        Redisson 做到了:
            分布式安全性;
            可重入 / 可中断;
            看门狗续期机制;
            公平锁 / 异步锁 / 读写锁等丰富锁模型;
            天然支持 Spring Boot 与注解式加锁(@RedissonLock 插件还有人封装);
            源码成熟、功能完备、开箱即用
        一切你想要的,Redisson 都有,
        一切你不想处理的,Redisson 替你兜底。

02.Redisson 的“看门狗”机制:程序员的贴身保镖
    Redisson 的锁有个神奇的地方:你只管加锁,逻辑没处理完,它会自动帮你续命。
    默认行为:
        使用 lock() 加锁时,Redisson 会启动一个后台线程;
        每 10 秒续一次命;
        每次把锁的过期时间续到 30 秒;
        只要你线程活着,它就一直守着锁。
        这就像你不小心把车停在了限时 30 分钟的车位上,看门狗每 10 分钟帮你喂个硬币进去续费……直到你走。
        只要逻辑未处理完,每隔10秒,续命30秒,无限重复

03.我能不能说“不用看门狗”?当然能!
    Redisson 不是你丈母娘,它不会一直管着你。
    如果你使用如下方式:
        csharp 体验AI代码助手 代码解读复制代码
        lock.lock(5, TimeUnit.SECONDS); // 锁 5 秒,不续期
    或者:
        csharp 体验AI代码助手 代码解读复制代码
        lock.tryLock(3, 5, TimeUnit.SECONDS); // 最多等3秒,加锁后锁5秒
    你就相当于告诉 Redisson:别续了,我知道自己几斤几两。

04.lock vs tryLock vs unlock,谁是核心戏骨?
    a.方法特点适合场景
        lock()阻塞等待,拿不到锁就等下去一定要拿到锁的业务
        lock(time, unit)阻塞,但限定持有时间(关闭看门狗)执行时间可控的业务
        tryLock()不等!拿不到锁立刻走高并发快速失败场景
        tryLock(wait, lease, unit)等待一段时间,拿到后最多持有 lease 时间推荐
        unlock()主动释放锁(别忘了!不然看门狗续你一脸)finally 里使用
    b.小提醒
        只有加锁成功的线程,才能 unlock()。否则你会看到报错:IllegalMonitorStateException,仿佛 Redisson 对你喊:“你谁啊?”

05.常用方法使用示例
    RLock lock = redissonClient.getLock("myLock");
    try {
        if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
            // 拿到锁后执行逻辑
        } else {
            // 没拿到锁,降级处理
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    } finally {
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }

06.Spring Boot 整合 Redisson 步骤(超简明)
    a.引入依赖
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.24.3</version> <!-- 版本可根据需要调整 -->
        </dependency>
    b.配置 application.yml
        redisson:
          config: |
            singleServerConfig:
              address: "redis://localhost:6379"
              password: null
            threads: 4
            nettyThreads: 4
    c.注入使用
        @Autowired
        private RedissonClient redissonClient;
        public void doSomething() {
            RLock lock = redissonClient.getLock("taskLock");
            lock.lock(); // or tryLock(...)
            try {
                // your biz logic
            } finally {
                lock.unlock();
            }
        }

07.还有哪些你没问但必须知道的关键知识点?
    a.可重入锁(ReentrantLock)
        Redisson 的 RLock 是可重入的,同一个线程加锁多次不会被锁死!
        csharp 体验AI代码助手 代码解读复制代码
        lock.lock(); // 第一次
        lock.lock(); // 第二次(也能拿到)
    b.公平锁 vs 非公平锁
        默认是非公平锁(谁快谁上)
        支持使用 getFairLock(...) 获取公平锁(按排队顺序)
    c.可中断锁
        csharp 体验AI代码助手 代码解读复制代码
        lock.lockInterruptibly();
        可响应 Thread.interrupt() 中断信号,适合线程池控制下的安全退出。
    d.异步加锁(带 Async 后缀)
        支持 tryLockAsync()、unlockAsync() 等异步调用方式,适合响应式编程。
    e.分布式锁的 RedLock?慎用!
        RedLock 要在多个 Redis 节点上都加锁才算成功,理论好,但实践中容错差,官方不建议在生产使用(尤其云 Redis 环境)!

1.41 [6]仅MySQL实现:唯一索引

00.总结
    a.唯一索引
        适用场景:低并发、简单锁定操作、短时间锁持有、无需自动超时机制
        典型场景:任务调度、确保资源独占访问
    b.悲观锁
        适用场景:高冲突、长业务操作、资源一致性要求高
        典型场景:金融交易、订单状态更新
    c.乐观锁
        适用场景:低冲突、高并发、短时间锁持有、允许重试获取锁
        典型场景:订单扣库存、数据表版本更新、用户抽奖等

00.基于唯一索引
    a.实现思路
        我们知道数据库表中的唯一索引可以确保一张表中相同数据只能插入一次
        基于这条规则我们可以创建一张表,然后给锁名字段创建一个唯一索引
        当并发插入时如果插入成功就获取到锁,插入失败就未获取到锁,释放锁就是把数据这条数据删除
    b.创建 union_key_lock 表
        CREATE TABLE `union_key_lock` (
          `id` bigint(20) NOT NULL AUTO_INCREMENT,
          `lock_name` varchar(255) NOT NULL DEFAULT '',
          `expire_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '锁过期时间',
          PRIMARY KEY (`id`),
          UNIQUE KEY `uidx_key_name` (`lock_name`) USING BTREE
        ) ENGINE=InnoDB  COMMENT='唯一键实现分布式锁'
        -----------------------------------------------------------------------------------------------------
        在 union_key_lock 表中将锁名字段 lock_name 添加唯一索引,expire_at 为锁过期时间
        可以在 Mysql 中或者项目中添加定时任务删除 expire_at<now() 的数据,防止代码出现异常未及时释放锁导致死锁
    c.代码实现
        基于数据库唯一索引代码实现起来是非常简单的,有两个方法
        第一个方法是 lock(),接收一个锁名参数和锁超时时间参数
        第二个方法是 unLock() 释放锁方法
        -----------------------------------------------------------------------------------------------------
        @Resource
        private JdbcTemplate jdbcTemplate;
        @Override
        public Boolean lock(String lockName, Integer second) {
            try {
                String sql = String.format("insert into union_key_lock (lock_name, expire_at) value ('%s','%s')", lockName, DateUtil.formatLocalDateTime(LocalDateTime.now().plusSeconds(second)));
                jdbcTemplate.execute(sql);
                return true;
            } catch (Exception e) {
                return false;
            }
        }
        @Override
        public void unLock(String lockName) {
            String sql = String.format("delete from union_key_lock where lock_name='%s';", lockName);
            jdbcTemplate.execute(sql);
        }
    d.测试代码:编写多线程代码压测是否成功实现分布式锁
        @Resource
        private UnionKeyLockImpl unionKeyLock;
        @Test
        void testunionKeyLock() {
            String lockName = "赵侠客";
            IntStream.range(1, 5).parallel().forEach(x -> {
                try {
                    if (unionKeyLock.lock(lockName, 5)) {
                        log.info("get lock success");
                    } else {
                        log.warn("get lock error");
                    }
                } finally {
                    unionKeyLock.unLock(lockName);
                }
            });
        }
    e.小结
        a.说明
            基于数据库的分布式锁优点包括实现简单、事务支持、无需额外组件和持久化特性
            缺点则包括性能较低、锁粒度受限、死锁风险、资源开销较大以及锁释放问题
        b.优点
            实现简单:基于数据库唯一索引
            事务支持:与业务操作同事务,保一致性
            无需额外组件:适用于已有数据库系统
            持久化:锁信息数据库存储,系统崩溃后仍存在
        c.缺点
            性能较低:相比内存级锁,SQL 操作性能低
            锁粒度受限:以表或行为单位,易竞争
            死锁风险:需严格事务管理
            资源开销:频繁获取锁增数据库负载
            锁释放问题:异常未释放需额外机制处理

1.42 [6]仅MySQL实现:悲观锁

00.总结
    a.唯一索引
        适用场景:低并发、简单锁定操作、短时间锁持有、无需自动超时机制
        典型场景:任务调度、确保资源独占访问
    b.悲观锁
        适用场景:高冲突、长业务操作、资源一致性要求高
        典型场景:金融交易、订单状态更新
    c.乐观锁
        适用场景:低冲突、高并发、短时间锁持有、允许重试获取锁
        典型场景:订单扣库存、数据表版本更新、用户抽奖等

01.基于悲观锁
    a.实现思路
        基于数据库悲观锁实现分布式锁依赖于数据库的行级锁机制
        通过 SELECT ... FOR UPDATE 等操作显式地锁定数据库中的某一行
        来达到获取分布式锁的目的。在这种方式下,其他事务在尝试修改这行数据时会被阻塞,直到锁被释放
    b.创建一张锁表,记录需要锁定的资源
        CREATE TABLE `select_for_update_lock` (
          `id` bigint(20) NOT NULL AUTO_INCREMENT,
          `lock_name` varchar(255) NOT NULL DEFAULT '',
          `lock_status` int(255) NOT NULL DEFAULT '0' COMMENT '0--正常 1--被锁',
          PRIMARY KEY (`id`),
          UNIQUE KEY `uidx_key_name` (`lock_name`) USING BTREE
        ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8 COMMENT='悲观锁'
        -----------------------------------------------------------------------------------------------------
        当获取锁时使用 FOR UPDATE 阻塞其它查询,任务执行完成后 COMMIT 提交事务后自动释放锁
        在调用锁之前要将锁名信息添加到表中
        -----------------------------------------------------------------------------------------------------
        BEGIN;
        SELECT * FROM select_for_update_lock WHERE lock_name = 'my_lock' AND lock_status = 0 FOR UPDATE;
        ...执行任务
        COMMIT;
        ---
        其他事务在尝试执行 SELECT ... FOR UPDATE 时会被阻塞,直到 COMMIT 后锁被释放
    c.代码实现
        @Resource
        private JdbcTemplate jdbcTemplate;
        @Resource
        private PlatformTransactionManager platformTransactionManager;
        public void lock(String lockName, Runnable runnable)  {
            DefaultTransactionDefinition def = new DefaultTransactionDefinition();
            def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
            def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
            TransactionStatus status = platformTransactionManager.getTransaction(def);
            try {
                jdbcTemplate.queryForObject("SELECT lock_name FROM select_for_update_lock WHERE lock_name = ? FOR UPDATE", String.class, lockName);
                runnable.run();;
            } catch (Exception e) {
                platformTransactionManager.rollback(status);
                throw e;
            }finally {
                platformTransactionManager.commit(status);
            }
        }
        -----------------------------------------------------------------------------------------------------
        在代码 lock() 方法中使用 PlatformTransactionManager 手动开启使用,在 finally 中手动提交事务
    d.测试代码
        @Resource
        private SelectForUpdateLockImpl selectForUpdateLock;
        @Test
        void testSelectForUpdateLock() {
            String lockName = "赵侠客";
            IntStream.range(1, 10).parallel().forEach(x -> {
                try {
                    selectForUpdateLock.lock(lockName, () -> {
                        log.info("get {} lock success", lockName);
                    });
                } catch (Exception e) {
                    log.error("get {} lock error", lockName);
                }
            });
        }
    e.小结
        a.优点
            实现简单:利用数据库行级锁机制,无需引入其他分布式锁组件
            事务支持:悲观锁与数据库事务结合紧密,能保证业务逻辑的原子性
            一致性强:依赖数据库锁机制,保证了高并发下数据的一致性
        b.缺点
            性能瓶颈:数据库行锁在高并发时可能成为性能瓶颈,导致数据库连接阻塞
            可用性受限:数据库故障或网络问题会影响锁的释放,降低系统可用性
            死锁风险:多事务复杂操作下可能产生死锁,需要精心设计锁策略
            锁粒度粗:行级锁可能导致锁竞争激烈,影响性能
            资源开销大:长期占用数据库资源,可能导致锁等待和连接池资源耗尽

1.43 [6]MySQL实现:乐观锁

00.总结
    a.唯一索引
        适用场景:低并发、简单锁定操作、短时间锁持有、无需自动超时机制
        典型场景:任务调度、确保资源独占访问
    b.悲观锁
        适用场景:高冲突、长业务操作、资源一致性要求高
        典型场景:金融交易、订单状态更新
    c.乐观锁
        适用场景:低冲突、高并发、短时间锁持有、允许重试获取锁
        典型场景:订单扣库存、数据表版本更新、用户抽奖等

01.基于乐观锁
    a.实现思路
        基于数据库的乐观锁实现分布式锁通常利用唯一索引或版本号机制来确保在高并发场景下的锁定操作
        乐观锁适合在冲突较少的场景中使用,依赖于更新时的数据状态一致性判断
    b.创建一张 optimistic_lock 表
        CREATE TABLE `optimistic_lock` (
          `id` bigint(20) NOT NULL AUTO_INCREMENT,
          `lock_name` varchar(50) DEFAULT NULL,
          `expire_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '锁过期时间',
          `lock_status` int(255) NOT NULL DEFAULT '0' COMMENT '0--正常 1--被锁',
           PRIMARY KEY (`id`),
           UNIQUE KEY `uidx_lock_name` (`lock_name`) USING BTREE
        ) ENGINE = InnoDB DEFAULT CHARSET=utf8 COMMENT='乐观锁实现分布式锁'
        -----------------------------------------------------------------------------------------------------
        在锁名字段上增加唯一索引,其实现思路是通过数据库的更新数据是否成功能判断是否获取到锁
        所以我们要提前将锁名任务添加到表中,expire_at 为锁过期时间,防止未及时释放导致死锁
        这里可以通过定时任务删除过期的锁
    c.代码实现
        @Resource
        private JdbcTemplate jdbcTemplate;
        public boolean lock(String lockName) {
            try {
                String sql = String.format("update optimistic_lock set lock_status=1, expire_at = NOW() + INTERVAL 1 MINUTE where lock_name ='%s' and lock_status = 0 ;", lockName);
                return jdbcTemplate.update(sql) == 1;
            } catch (Exception e) {
                return false;
            }
        }
        public void unLock(String lockName) {
            String sql = String.format("update optimistic_lock set lock_status=0 ,expire_at=now() where lock_name='%s' ;", lockName);
            jdbcTemplate.update(sql);
        }
    d.测试代码
        @Resource
        private OptimisticLock optimisticLock;
        @Test
        void testOptimisticLock() {
            String lockName = "赵侠客";
            IntStream.range(1, 10).parallel().forEach(x -> {
                try {
                    if (optimisticLock.lock(lockName)) {
                        log.info("get lock success");
                    } else {
                        log.warn("get lock error");
                    }
                } finally {
                    optimisticLock.unLock(lockName);
                }
            });
        }
    e.小结
        a.优点
            实现简单:易于理解和实现,可以直接利用现有数据库,无需额外分布式中间件
            数据库天然一致性:利用数据库的事务和一致性机制,保证并发场景下的数据一致性
            适用于小规模系统:对于低并发系统,乐观锁可以有效满足需求,避免引入复杂中间件
        b.缺点
            性能瓶颈:数据库不适合处理高并发锁操作,频繁的读写操作会给数据库带来压力
            冲突处理复杂:乐观锁在冲突时需要重试,可能导致操作延迟
            锁粒度问题:基于记录的锁粒度较粗,可能导致资源争用
            不适合高并发场景:高并发下冲突率增加,重试操作影响性能和响应时间
            数据库单点问题:依赖单个数据库节点可能导致单点故障
            锁过期处理复杂:数据库锁缺乏自动过期机制,可能导致操作阻塞

2 悲观锁

2.1 定义:读多写少

01.悲观锁:读多写少
    认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改
    因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观地认为,不加锁的并发操作一定会出问题

2.2 实现:4种

00.汇总
    synchronized                      Java内置关键字,用于方法或代码块的同步
    ReentrantLock                     可重入锁,提供更灵活的锁控制                          公平锁/非公平锁(默认)
    ReentrantReadWriteLock            读写锁,允许多个读线程并发访问,写线程独占锁           读锁/写锁
    StampedLock                       时间戳锁,比ReentrantReadWriteLock性能高,JDK8引入    读锁/写锁/乐观锁

01.synchronized
    定义         Java内置关键字,用于方法或代码块的同步
    加锁         synchronized
    解锁         自动解锁
    等待与唤醒   Object.wait() / Object.notify()
    可重入性     是
    可中断性     不支持
    公平性       不支持
    性能         较低(锁竞争时)

02.ReentrantLock
    定义         可重入锁,提供更灵活的锁控制,非公平锁(默认)
    加锁         lock()
    解锁         unlock()
    等待与唤醒   Condition.await() / Condition.signal()
    可重入性     是
    可中断性     lockInterruptibly()
    公平性       可选(公平锁)
    性能         较高(灵活控制)
    ---------------------------------------------------------------------------------------------------------
    公平锁:确保线程按照请求顺序获取锁
    非公平锁:允许线程“插队”,可能导致某些线程长时间等待

03.ReentrantReadWriteLock
    定义         读写锁,允许多个读线程并发访问,写线程独占锁
    加锁         readLock().lock() / writeLock().lock()
    解锁         readLock().unlock() / writeLock().unlock()
    等待与唤醒   Condition中的await()、signalAll()
    可重入性     是
    可中断性     不支持
    公平性       不支持
    性能         较高(灵活控制)
    ---------------------------------------------------------------------------------------------------------
    读锁:共享锁,允许多个线程并发读取
    写锁:独占锁,只允许一个线程独占,写线程在持有锁时阻止其他线程的读写操作

04.StampedLock
    定义         时间戳锁,改进的读写锁,提供乐观读锁和悲观读锁两种模式,JDK8引入
    加锁         writeLock() / readLock() / tryOptimisticRead()
    解锁         unlockWrite(stamp) / unlockRead(stamp)
    等待与唤醒   无内置等待与唤醒机制
    可重入性     是
    可中断性     不支持
    公平性       不支持
    性能         较高(适用于高并发)
    ---------------------------------------------------------------------------------------------------------
    写锁:独占锁,确保写操作的原子性,适用于需要修改共享资源的场景
    读锁:共享锁,允许多个线程并发读取,适用于读操作频繁的场景
    乐观读锁:允许在不加锁的情况下读取数据,假设不会发生写操作,适用于读多写少的场景

2.3 [0]unsaft:魔法类

01.定义
    Unsafe类提供了一组用于执行低级别、不安全操作的方法
    这些方法允许开发者直接操作内存、线程和其他底层资源
    由于这些操作可能会破坏Java的安全性和稳定性,Unsafe类的使用受到严格限制

02.特点
    安全性:Unsafe的操作可能会破坏Java的内存安全性,导致程序崩溃或数据损坏
    可移植性:Unsafe是一个内部类,不保证在所有JVM实现中都可用
    使用限制:由于其潜在的危险性,Unsafe的使用通常受到限制,建议仅在非常必要的情况下使用

03.原理
    Unsafe 类通过 JNI(Java Native Interface)调用底层的本地方法,直接与操作系统和硬件交互
    它提供了一些关键功能,如内存分配、CAS(Compare-And-Swap)操作、线程挂起和恢复等

04.常用API:由于 Unsafe 是一个内部类,通常不建议直接使用它,然而,它提供了一些关键方法
    a.内存操作
        allocateMemory(long bytes): 分配指定大小的内存
        freeMemory(long address): 释放指定地址的内存
        putInt(long address, int value): 将一个整数写入指定的内存地址
        getInt(long address): 从指定的内存地址读取一个整数
    b.CAS操作
        compareAndSwapInt(Object obj, long offset, int expected, int update): 对指定对象的整数字段执行 CAS 操作
    c.对象操作
        objectFieldOffset(Field field): 获取对象字段的内存偏移量
        getObject(Object obj, long offset): 从对象的指定偏移量读取对象引用
        putObject(Object obj, long offset, Object value): 将对象引用写入对象的指定偏移量
    d.线程操作
        park(boolean isAbsolute, long time): 挂起当前线程
        unpark(Thread thread): 恢复指定线程

05.使用步骤
    a.获取Unsafe实例
        由于Unsafe的构造函数是私有的,通常通过反射获取其实例
    b.执行低级操作
        使用Unsafe提供的方法执行内存、对象或线程操作
    c.注意安全性
        由于Unsafe的操作可能会破坏JVM的安全性和稳定性,使用时需格外小心

06.应用场景及代码示例
    a.获取Unsafe实例
        import sun.misc.Unsafe;

        import java.lang.reflect.Field;

        public class UnsafeExample {
            public static Unsafe getUnsafe() {
                try {
                    Field field = Unsafe.class.getDeclaredField("theUnsafe");
                    field.setAccessible(true);
                    return (Unsafe) field.get(null);
                } catch (Exception e) {
                    throw new RuntimeException("Unable to get Unsafe instance", e);
                }
            }
        }
    b.内存操作
        public class MemoryOperationExample {
            public static void main(String[] args) {
                Unsafe unsafe = UnsafeExample.getUnsafe();
                long address = unsafe.allocateMemory(4); // Allocate 4 bytes
                try {
                    unsafe.putInt(address, 42); // Write an integer to the allocated memory
                    int value = unsafe.getInt(address); // Read the integer from memory
                    System.out.println("Value: " + value);
                } finally {
                    unsafe.freeMemory(address); // Free the allocated memory
                }
            }
        }
    c.CAS操作
        import java.lang.reflect.Field;

        public class CASOperationExample {
            private volatile int value = 0;

            public static void main(String[] args) throws NoSuchFieldException {
                Unsafe unsafe = UnsafeExample.getUnsafe();
                CASOperationExample example = new CASOperationExample();

                Field valueField = CASOperationExample.class.getDeclaredField("value");
                long offset = unsafe.objectFieldOffset(valueField);

                boolean success = unsafe.compareAndSwapInt(example, offset, 0, 42);
                System.out.println("CAS success: " + success);
                System.out.println("New value: " + example.value);
            }
        }
    d.线程操作
        public class ThreadOperationExample {
            public static void main(String[] args) {
                Unsafe unsafe = UnsafeExample.getUnsafe();
                Thread thread = new Thread(() -> {
                    System.out.println("Thread is going to park.");
                    unsafe.park(false, 0);
                    System.out.println("Thread is unparked.");
                });

                thread.start();

                try {
                    Thread.sleep(1000); // Simulate some work
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }

                System.out.println("Main thread is going to unpark the thread.");
                unsafe.unpark(thread);
            }
        }

2.4 [0]volatile:不是锁,是同步机制

00.volatile不是悲观锁?
    a.回答
        volatile并不是一种锁机制,无论是悲观锁还是乐观锁
        它是一种轻量级的同步机制,确保变量的可见性和防止指令重排序
    b.锁机制
        悲观锁:通过加锁机制确保数据的独占访问,适用于写操作频繁且冲突较多的场景
        乐观锁:通过无锁机制和冲突检测来确保数据的一致性,适用于读操作频繁且冲突较少的场景
        volatile:不加锁,主要用于确保变量的可见性和防止指令重排序
    c.阻塞与非阻塞
        悲观锁:阻塞机制,线程在获取锁之前会被阻塞
        乐观锁:非阻塞机制,线程不会因为锁而阻塞,但可能会因为冲突而重试
        volatile:非阻塞机制,不会阻塞线程
    d.适用场景
        悲观锁:适用于写操作频繁且冲突较多的场景
        乐观锁:适用于读操作频繁且冲突较少的场景
        volatile:适用于需要确保变量可见性和防止指令重排序的场景,但不适用于需要原子性操作的场景

01.volatile
    a.定义
        1.【保证变量可见性】
        2.【防止局部重排序】
    b.特性
        不保证原子性,不会引起线程阻塞
    c.使用场景
        适用于状态标志等简单的读写操作,不适用于复合操作(如自增、自减)
    d.性能
        开销较小,因为不会引起线程上下文切换
    e.示例
        public class VolatileExample {
            private volatile boolean flag = true;
            public void setFlag(boolean flag) {
                this.flag = flag;
            }
            public boolean getFlag() {
                return flag;
            }
        }

02.常用API
    a.用法
        volatile关键字本身没有特定的API,它是一个修饰符,用于修饰变量
    b.常见的使用方式
        public class VolatileExample {
            private volatile boolean flag = false;

            public void setFlag(boolean flag) {
                this.flag = flag;
            }

            public boolean getFlag() {
                return flag;
            }
        }
    c.内存屏障
        LoadLoad Barrier:确保在屏障之前的所有加载操作(读操作)在屏障之后的加载操作之前完成
        StoreStore Barrier:确保在屏障之前的所有存储操作(写操作)在屏障之后的存储操作之前完成
        LoadStore Barrier:确保在屏障之前的所有加载操作在屏障之后的存储操作之前完成
        StoreLoad Barrier:确保在屏障之前的所有存储操作在屏障之后的加载操作之前完成
    d.内存屏障规则
        a.写volatile变量时
            在写操作之前插入一个StoreStore Barrier,确保在写volatile变量之前的所有普通写操作都已经完成
            在写操作之后插入一个StoreLoad Barrier,确保后续的读操作不会被重排序到写操作之前
        b.读volatile变量时
            在读操作之前插入一个LoadLoad Barrier,确保之前的读操作已经完成
            在读操作之后插入一个LoadStore Barrier,确保后续的写操作不会被重排序到读操作之前

03.应用场景
    a.状态标志:在多线程环境下,使用 volatile 关键字来修饰状态标志变量,确保状态的变化对所有线程立即可见
        public class StatusFlag {
            private volatile boolean running = true;

            public void stop() {
                running = false;
            }

            public void run() {
                while (running) {
                    // 执行任务
                }
            }
        }
    b.双重检查锁定:在实现单例模式的双重检查锁定时,使用 volatile 关键字来修饰实例变量,防止指令重排序
        public class Singleton {
            private static volatile Singleton instance;

            private Singleton() {}

            public static Singleton getInstance() {
                if (instance == null) {
                    synchronized (Singleton.class) {
                        if (instance == null) {
                            instance = new Singleton();
                        }
                    }
                }
                return instance;
            }
        }
    c.轻量级读写锁:在某些情况下,可以使用 volatile 变量来实现轻量级的读写锁
        public class LightWeightReadWriteLock {
            private volatile int readers = 0;

            public void readLock() {
                while (true) {
                    int currentReaders = readers;
                    if (currentReaders >= 0 && compareAndSet(currentReaders, currentReaders + 1)) {
                        break;
                    }
                }
            }

            public void readUnlock() {
                readers--;
            }

            private boolean compareAndSet(int expected, int newValue) {
                // 模拟CAS操作
                if (readers == expected) {
                    readers = newValue;
                    return true;
                }
                return false;
            }
        }

2.5 [0]Condition:等待通知条件,与Lock接口结合使用

01.定义
    Condition是一个用于线程间协调的工具,允许线程在某个条件上等待,直到被其他线程通知
    它通常与Lock一起使用,以替代synchronized关键字和Object类中的wait()、notify()和notifyAll()方法的功能

02.原理
    Condition 的工作原理基于条件变量。线程可以在条件变量上等待,直到某个条件为真
    其他线程可以在条件满足时通知等待的线程
    Condition 必须与 Lock 一起使用,因为它依赖于锁的获取和释放来管理线程的等待和唤醒

03.常用API
    a.创建条件
        Condition newCondition(): 在 Lock 接口中定义,用于创建一个新的 Condition 实例
    b.方法
        void await(): 使当前线程等待,直到被通知或被中断
        void signal(): 唤醒一个等待线程
        void signalAll(): 唤醒所有等待线程
        boolean await(long time, TimeUnit unit): 使当前线程等待指定时间,直到被通知或被中断

04.使用步骤
    a.创建锁和条件
        使用 Lock 接口创建锁,并通过 newCondition() 方法创建 Condition。=
    b.等待条件
        在需要等待的线程中,获取锁并调用 await() 方法
    c.通知条件
        在其他线程中,获取锁并调用 signal() 或 signalAll() 方法
    d.释放锁
        确保在 finally 块中释放锁,以避免死锁

05.应用1:使用 synchronized 和 Object 的 wait() / notify()
    a.说明
        在使用 synchronized 时,Condition 的功能由 Object 的 wait() 和 notify() 方法提供
    b.代码
        public class SynchronizedExample {
            private final Object lock = new Object();
            private boolean conditionMet = false;

            public void awaitCondition() throws InterruptedException {
                synchronized (lock) {
                    while (!conditionMet) {
                        lock.wait();
                    }
                    // Perform action when condition is met
                }
            }

            public void signalCondition() {
                synchronized (lock) {
                    conditionMet = true;
                    lock.notify();
                }
            }
        }

06.应用2:使用 ReentrantLock 和 Condition
    a.说明
        ReentrantLock 提供了更灵活的锁机制,并允许创建多个 Condition 对象
    b.代码
        import java.util.concurrent.locks.Condition;
        import java.util.concurrent.locks.Lock;
        import java.util.concurrent.locks.ReentrantLock;

        public class ReentrantLockExample {
            private final Lock lock = new ReentrantLock();
            private final Condition condition = lock.newCondition();
            private boolean conditionMet = false;

            public void awaitCondition() throws InterruptedException {
                lock.lock();
                try {
                    while (!conditionMet) {
                        condition.await();
                    }
                    // Perform action when condition is met
                } finally {
                    lock.unlock();
                }
            }

            public void signalCondition() {
                lock.lock();
                try {
                    conditionMet = true;
                    condition.signal();
                } finally {
                    lock.unlock();
                }
            }
        }

07.应用3:使用 ReentrantReadWriteLock 和 Condition
    a.说明
        ReentrantReadWriteLock 提供了读写锁,通常用于读多写少的场景,Condition 通常与写锁一起使用
    b.代码
        import java.util.concurrent.locks.Condition;
        import java.util.concurrent.locks.ReentrantReadWriteLock;

        public class ReentrantReadWriteLockExample {
            private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
            private final Condition condition = rwLock.writeLock().newCondition();
            private boolean conditionMet = false;

            public void awaitCondition() throws InterruptedException {
                rwLock.writeLock().lock();
                try {
                    while (!conditionMet) {
                        condition.await();
                    }
                    // Perform action when condition is met
                } finally {
                    rwLock.writeLock().unlock();
                }
            }

            public void signalCondition() {
                rwLock.writeLock().lock();
                try {
                    conditionMet = true;
                    condition.signal();
                } finally {
                    rwLock.writeLock().unlock();
                }
            }
        }

04.应用4:使用 StampedLock
    a.介绍
        StampedLock 提供了一种乐观读锁和悲观读写锁的组合,但它不支持 Condition
        因此,StampedLock 不适用于 Condition 的使用场景
        StampedLock 更适合用于需要高效读操作的场景,
        但如果需要条件等待和通知机制,建议使用 ReentrantLock 或 ReentrantReadWriteLock
    b.代码
        import java.util.concurrent.locks.StampedLock;

        public class StampedLockExample {
            private final StampedLock stampedLock = new StampedLock();
            private boolean conditionMet = false;

            public void awaitCondition() throws InterruptedException {
                long stamp = stampedLock.writeLock();
                try {
                    while (!conditionMet) {
                        // StampedLock does not support Condition, so manual handling is needed
                        stampedLock.unlockWrite(stamp);
                        Thread.sleep(100); // Simulate waiting
                        stamp = stampedLock.writeLock();
                    }
                    // Perform action when condition is met
                } finally {
                    stampedLock.unlockWrite(stamp);
                }
            }

            public void signalCondition() {
                long stamp = stampedLock.writeLock();
                try {
                    conditionMet = true;
                    // No direct equivalent of signal() in StampedLock
                } finally {
                    stampedLock.unlockWrite(stamp);
                }
            }
        }

2.6 [0]LockSupport:线程阻塞唤醒类,底层依赖Unsafe类

01.定义
    LockSupport 是一个用于创建锁和其他同步类的基本线程阻塞原语
    它通过许可(permit)的概念来管理线程的阻塞和唤醒,每个线程都有一个与之关联的许可

02.原理
    许可机制
    每个线程都有一个与之关联的许可,许可的状态只有两种:有许可和无许可
    LockSupport的park()和unpark()方法是基于许可的,许可是一次性的。如果在park()之前调用unpark(),则park()不会阻塞
    LockSupport不会抛出InterruptedException,但可以通过Thread.interrupted()检查中断状态
    LockSupport提供了更底层的线程阻塞和唤醒机制,适用于需要精细控制线程状态的场景。

03.常用API
    void park(): 阻塞当前线程,直到获得许可
    void parkNanos(long nanos): 阻塞当前线程指定的纳秒时间
    void parkUntil(long deadline): 阻塞当前线程直到指定的绝对时间
    void unpark(Thread thread): 唤醒指定线程,授予其许可

04.使用步骤
    阻塞线程:在需要阻塞的地方调用LockSupport.park()
    唤醒线程:在需要唤醒的地方调用LockSupport.unpark(Thread thread),传入需要唤醒的线程对象

05.应用场景及代码示例
    a.简单的线程阻塞和唤醒
        import java.util.concurrent.locks.LockSupport;

        public class LockSupportExample {
            public static void main(String[] args) {
                Thread thread = new Thread(() -> {
                    System.out.println("Thread is going to park.");
                    LockSupport.park();
                    System.out.println("Thread is unparked.");
                });

                thread.start();

                try {
                    Thread.sleep(1000); // Simulate some work
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }

                System.out.println("Main thread is going to unpark the thread.");
                LockSupport.unpark(thread);
            }
        }
    b.带超时的线程阻塞
        import java.util.concurrent.locks.LockSupport;

        public class LockSupportTimeoutExample {
            public static void main(String[] args) {
                Thread thread = new Thread(() -> {
                    System.out.println("Thread is going to park for 2 seconds.");
                    LockSupport.parkNanos(2000 * 1000000L); // Park for 2 seconds
                    System.out.println("Thread is unparked or timeout.");
                });

                thread.start();
            }
        }
    c.线程间的简单通信
        import java.util.concurrent.locks.LockSupport;

        public class LockSupportCommunicationExample {
            public static void main(String[] args) {
                Thread thread1 = new Thread(() -> {
                    System.out.println("Thread 1 is going to park.");
                    LockSupport.park();
                    System.out.println("Thread 1 is unparked.");
                });

                Thread thread2 = new Thread(() -> {
                    System.out.println("Thread 2 is going to unpark Thread 1.");
                    LockSupport.unpark(thread1);
                });

                thread1.start();
                try {
                    Thread.sleep(1000); // Ensure thread1 parks before thread2 unparks
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                thread2.start();
            }
        }

2.7 [1]synchronized:3类

00.synchronized
    a.定义
        1.保证【代码块或方法的原子性和可见性】
        2.保证【同一时刻只有一个线程能执行被 synchronized 修饰的代码块或方法】
    b.作用范围
        修饰普通方法,为当前对象(this)加锁,进入同步代码前要获得当前对象的锁
        修饰静态方法,为当前类加锁(锁的是 Class 对象),进入同步代码前要获得当前类的锁
        修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁
    c.API
        a.加锁
            方法锁:public synchronized void method() { ... }
            代码块锁:synchronized (this) { ... }
        b.解锁
            自动解锁:在方法结束或代码块结束时自动释放锁,或出现异常
        c.等待与唤醒
            不支持直接的等待和唤醒机制,使用Object.wait()和Object.notify()/notifyAll()进行线程间通信
    d.使用this对象(代表当前实例),或者当前类的Class对象作为锁
        a.this,当前实例对象锁
            synchronized(this){
                for(int j=0;j<1000000;j++){
                    i++;
                }
            }
        b.Class对象锁
            synchronized(AccountingSync.class){
                for(int j=0;j<1000000;j++){
                    i++;
                }
            }

01.修饰普通方法,为当前对象(this)加锁,进入同步代码前要获得当前对象的锁
    a.语法
        a.介绍
            public synchronized void method() {
                // .......
            }
        b.说明
            当 synchronized 修饰普通方法时,被修饰的方法被称为同步方法,其作用范围是整个方法,作用的对象是调用这个方法的对象
    b.代码
        a.介绍
            通过在方法声明中加入 synchronized 关键字,可以保证在任意时刻,只有一个线程能执行该方法
        b.代码
            public class AccountingSync implements Runnable {
                //共享资源(临界资源)
                static int i = 0;
                // synchronized 同步方法
                public synchronized void increase() {
                    i ++;
                }
                @Override
                public void run() {
                    for(int j=0;j<1000000;j++){
                        increase();
                    }
                }
                public static void main(String args[]) throws InterruptedException {
                    AccountingSync instance = new AccountingSync();
                    Thread t1 = new Thread(instance);
                    Thread t2 = new Thread(instance);
                    t1.start();
                    t2.start();
                    t1.join();
                    t2.join();
                    System.out.println("static, i output:" + i);
                }
            }
            /**
             * 输出结果:
             * static, i output:2000000
             */
            -------------------------------------------------------------------------------------------------
            如果在方法 increase() 前不加 synchronized,因为 i++ 不具备原子性,所以最终结果会小于 2000000

02.修饰静态方法,为当前类加锁(锁的是 Class 对象),进入同步代码前要获得当前类的锁
    a.语法
        a.介绍
            public static synchronized void staticMethod() {
                // .......
            }
        b.说明
            当 synchronized 修饰静态的方法时,其作用的范围是整个方法,作用对象是调用这个类的所有对象。
    b.代码
        a.介绍
            当 synchronized 同步静态方法时,锁的是当前类的 Class 对象,不属于某个对象
            当前类的 Class 对象锁被获取,不影响实例对象锁的获取,两者互不影响,本质上是 this 和 Class 的不同
            由于静态成员变量不专属于任何一个对象,因此通过 Class 锁可以控制静态成员变量的并发操作
        b.代码
            public class AccountingSyncClass implements Runnable {
                static int i = 0;
                /**
                 * 同步静态方法,锁是当前class对象,也就是
                 * AccountingSyncClass类对应的class对象
                 */
                public static synchronized void increase() {
                    i++;
                }
                // 非静态,访问时锁不一样不会发生互斥
                public synchronized void increase4Obj() {
                    i++;
                }
                @Override
                public void run() {
                    for(int j=0;j<1000000;j++){
                        increase();
                    }
                }
                public static void main(String[] args) throws InterruptedException {
                    //new新实例
                    Thread t1=new Thread(new AccountingSyncClass());
                    //new新实例
                    Thread t2=new Thread(new AccountingSyncClass());
                    //启动线程
                    t1.start();t2.start();
                    t1.join();t2.join();
                    System.out.println(i);
                }
            }
            /**
             * 输出结果:
             * 2000000
             */
            -------------------------------------------------------------------------------------------------
            需要注意的是如果线程 A 调用了一个对象的非静态 synchronized 方法,
            线程 B 需要调用这个对象所属类的静态 synchronized 方法,是不会发生互斥的,
            因为访问静态 synchronized 方法占用的锁是当前类的 Class 对象,
            而访问非静态 synchronized 方法占用的锁是当前对象(this)的锁
            -------------------------------------------------------------------------------------------------
            由于 synchronized 关键字同步的是静态的 increase 方法,与同步实例方法不同的是,其锁对象是当前类的 Class 对象
            注意代码中的 increase4Obj 方法是实例方法,其对象锁是当前实例对象(this),
            如果别的线程调用该方法,将不会产生互斥现象,毕竟锁的对象不同,
            这种情况下可能会发生线程安全问题(操作了共享静态变量 i)

03.修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁
    a.语法
        a.介绍
            public void classMethod() throws InterruptedException {
                // 前置代码...
                // 加锁代码
                synchronized (SynchronizedExample.class) {
                    // ......
                }
                // 后置代码...
            }
        b.说明
            一个方法中的某个部分使用 synchronized 来修饰(一段代码块),从而实现对一个方法中的部分代码进行加锁
            以上代码在执行时,被修饰的代码块称为同步语句块,其作用范围是大括号“{}”括起来的代码块,作用的对象是调用这个代码块的对象。
    b.代码
        a.介绍
            某些情况下,我们编写的方法代码量比较多,存在一些比较耗时的操作,而需要同步的代码块只有一小部分,
            如果直接对整个方法进行同步,可能会得不偿失,此时我们可以使用同步代码块的方式对需要同步的代码进行包裹
        b.代码
            public class AccountingSync2 implements Runnable {
                static AccountingSync2 instance = new AccountingSync2(); // 饿汉单例模式

                static int i=0;

                @Override
                public void run() {
                    //省略其他耗时操作....
                    //使用同步代码块对变量i进行同步操作,锁对象为instance
                    synchronized(instance){
                        for(int j=0;j<1000000;j++){
                            i++;
                        }
                    }
                }

                public static void main(String[] args) throws InterruptedException {
                    Thread t1=new Thread(instance);
                    Thread t2=new Thread(instance);
                    t1.start();t2.start();
                    t1.join();t2.join();
                    System.out.println(i);
                }
            }
            /**
             * 输出结果:
             * 2000000
             */
            -------------------------------------------------------------------------------------------------
            我们将 synchronized 作用于一个给定的实例对象 instance,即当前实例对象就是锁的对象,
            当线程进入 synchronized 包裹的代码块时就会要求当前线程持有 instance 实例对象的锁,
            如果当前有其他线程正持有该对象锁,那么新的线程就必须等待,这样就保证了每次只有一个线程执行 i++ 操作

2.8 [1]synchronized:监视器

01.介绍
    monitor 可以被看作是一种守门人或保安,它确保同一时刻只有一个线程可以访问受保护的代码段
    你可以将它想象成一个房间的门,门的里面有一些重要的东西,而 monitor 就是那个保护门的保安

02.工作方式
    a.进入房间
        当一个线程想要进入受保护的代码区域(房间)时,它必须得到 monitor 的允许
        如果房间里没有其他线程,monitor 会让它进入并关闭门
    b.等待其他线程
        如果房间里已经有一个线程,其他线程就必须等待
        monitor 会让其他线程排队等候,直到房间里的线程完成工作离开房间
    c.离开房间
        当线程完成它的工作并离开受保护的代码区域时,monitor 会重新打开门,并让等待队列中的下一个线程进入
    d.协调线程
        monitor 还可以通过一些特殊的机制(例如 wait 和 notify 方法,讲 Condtion 的时候会细讲)来协调线程之间的合作
        线程可以通过 monitor 来发送信号告诉其他线程现在可以执行某些操作了

2.9 [1]synchronized:对象头

01.定义
    按照常规理解,识别线程 ID 需要一组 mapping 映射关系来搞定
    如果单独维护这个 mapping 关系又要考虑线程安全的问题
    根据奥卡姆剃刀原理,Java 万物皆是对象,对象皆可用作锁
    与其单独维护一个 mapping 关系,不如中心化将锁的信息维护在 Java 对象本身上

02.Java对象头最多由三部分构成
    1.MarkWord
    2.ClassMetadata Address
    3.Array Length(如果对象是数组才会有这部分)

2.10 [1]synchronized:实现原理

01.回答
    a.JVM中的监视器锁,Monitor Lock
        每个对象都有一个与之关联的监视器锁(Monitor)
        监视器锁是 JVM 实现线程同步的基础,它可以用来实现互斥访问
        当一个线程试图进入一个被 synchronized 修饰的方法或代码块时,它必须首先获得该对象的监视器锁。如果监视器锁已经被其他线程持有,那么当前线程将被阻塞,直到监视器锁被释放
    b.对象头,Object Header
        每个 Java 对象在内存中都有一个对象头,其中包含了对象的元数据
        对象头中有一个叫做 Mark Word 的字段,用于存储对象的锁信息、哈希码、GC 状态等
        当一个对象被锁定时,Mark Word 中会存储锁的状态和持有锁的线程信息

02.工作原理
    a.锁对象和监视器对象
        每个 Java 对象都有一个与之关联的锁对象
        每个锁对象都有一个监视器对象,包含锁的状态信息和等待队列
    b.锁的状态和操作
        偏向锁:通过在对象头的 Mark Word 中记录持有锁的线程 ID 来实现,适用于线程独占锁的场景
        轻量级锁:通过在对象头的 Mark Word 中记录锁记录来实现,适用于锁竞争较少的场景
        重量级锁:使用操作系统的互斥量来实现,适用于高竞争场景
    c.锁的优化机制
        自适应自旋:在锁竞争时进行一段时间的自旋,尝试获取锁,减少阻塞和唤醒的开销
        监视器对象的等待队列和条件等待队列:未竞争到锁的线程存储在等待队列中,获得锁的线程调用 wait 方法后存放在条件等待队列中,解锁和通知会唤醒相应队列中的等待线程来争抢锁

2.11 [1]synchronized:四种状态

00.四种状态
    无锁状态:没有线程竞争,锁处于无锁状态
    偏向锁:当一个线程第一次获取锁时,锁会偏向该线程,后续该线程再次获取锁时无需进行同步操作
    轻量级锁:当多个线程竞争锁时,锁会升级为轻量级锁,使用自旋锁来避免线程阻塞
    重量级锁:当自旋锁无法解决竞争问题时,锁会升级为重量级锁,使用操作系统的互斥量(mutex)来实现线程同步

01.无锁状态(Unlocked)
    a.描述
        对象没有被任何线程锁定
    b.特点
        所有线程都可以自由访问该对象,不需要进行任何同步操作
    c.适用场景
        对象未被任何线程访问或修改
    d.代码
        public class NoLockExample {
            private int count = 0;

            public void increment() {
                count++;
            }

            public int getCount() {
                return count;
            }

            public static void main(String[] args) {
                NoLockExample example = new NoLockExample();

                // 启动多个线程进行并发操作
                for (int i = 0; i < 10; i++) {
                    new Thread(() -> {
                        for (int j = 0; j < 1000; j++) {
                            example.increment();
                        }
                    }).start();
                }

                // 等待所有线程完成
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                // 打印最终的计数值
                System.out.println("Final count: " + example.getCount());
            }
        }
        -----------------------------------------------------------------------------------------------------
        无锁状态是指对象没有被任何线程锁定,所有线程都可以自由访问该对象,不需要进行任何同步操作

02.偏向锁状态(Biased Locking)
    a.描述
        对象被一个线程锁定,并且该线程在接下来的执行中不会遇到竞争
    b.实现
        偏向锁通过在对象头的 Mark Word 中记录持有锁的线程 ID 来实现
        获取锁时,线程使用 CAS 操作将自己的线程 ID 写入对象头
        如果对象头中的线程 ID 与当前线程 ID 匹配,则表示当前线程已经持有锁,无需进行任何同步操作
    c.特点
        偏向锁的获取和释放操作非常轻量级,适用于线程独占锁的场景
    d.适用场景
        锁竞争较少的场景
    e.代码
        public class BiasedLockExample {
            private int count = 0;

            public synchronized void increment() {
                count++;
            }

            public int getCount() {
                return count;
            }

            public static void main(String[] args) {
                BiasedLockExample example = new BiasedLockExample();

                // 启动一个线程进行操作,触发偏向锁
                Thread t1 = new Thread(() -> {
                    for (int j = 0; j < 1000; j++) {
                        example.increment();
                    }
                });
                t1.start();

                // 等待线程完成
                try {
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                // 打印最终的计数值
                System.out.println("Final count: " + example.getCount());
            }
        }
        -----------------------------------------------------------------------------------------------------
        偏向锁状态是指对象被一个线程锁定,并且该线程在接下来的执行中不会遇到竞争
        偏向锁通过在对象头的 Mark Word 中记录持有锁的线程 ID 来实现

03.轻量级锁状态(Lightweight Locking)
    a.描述
        对象被一个线程锁定,但可能会遇到竞争
    b.实现
        轻量级锁通过在对象头的 Mark Word 中记录锁记录(Lock Record)来实现
        获取锁时,线程使用 CAS 操作将锁记录写入对象头
        如果 CAS 操作成功,则表示当前线程获取了锁;如果 CAS 操作失败,则表示存在竞争,锁升级为重量级锁
    c.特点
        轻量级锁的获取和释放操作相对偏向锁稍重,但仍然比重量级锁轻量级
    d.适用场景
        锁竞争较少,但存在一定竞争的场景。
    e.代码
        public class LightweightLockExample {
            private int count = 0;

            public synchronized void increment() {
                count++;
            }

            public int getCount() {
                return count;
            }

            public static void main(String[] args) {
                LightweightLockExample example = new LightweightLockExample();

                // 启动多个线程进行并发操作,触发轻量级锁
                for (int i = 0; i < 2; i++) {
                    new Thread(() -> {
                        for (int j = 0; j < 1000; j++) {
                            example.increment();
                        }
                    }).start();
                }

                // 等待所有线程完成
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                // 打印最终的计数值
                System.out.println("Final count: " + example.getCount());
            }
        }
        -----------------------------------------------------------------------------------------------------
        轻量级锁状态是指对象被一个线程锁定,但可能会遇到竞争
        轻量级锁通过在对象头的 Mark Word 中记录锁记录(Lock Record)来实现

04.重量级锁状态(Heavyweight Locking)
    a.描述
        对象被多个线程竞争锁定,使用操作系统的互斥量(Mutex)来实现
    b.实现
        重量级锁依赖于操作系统的阻塞和唤醒机制
        当一个线程获取锁时,如果锁已经被其他线程持有,则当前线程会被阻塞,进入等待队列
        持有锁的线程释放锁时,会唤醒等待队列中的线程,使其重新竞争锁
    c.特点
        重量级锁的获取和释放操作开销较大,适用于高竞争场景
    d.适用场景
        锁竞争激烈的场景
    e.代码
        public class HeavyweightLockExample {
            private int count = 0;

            public synchronized void increment() {
                count++;
            }

            public int getCount() {
                return count;
            }

            public static void main(String[] args) {
                HeavyweightLockExample example = new HeavyweightLockExample();

                // 启动多个线程进行并发操作,触发重量级锁
                for (int i = 0; i < 10; i++) {
                    new Thread(() -> {
                        for (int j = 0; j < 1000; j++) {
                            example.increment();
                        }
                    }).start();
                }

                // 等待所有线程完成
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                // 打印最终的计数值
                System.out.println("Final count: " + example.getCount());
            }
        }
        -----------------------------------------------------------------------------------------------------
        重量级锁状态是指对象被多个线程竞争锁定,使用操作系统的互斥量(Mutex)来实现
        重量级锁依赖于操作系统的阻塞和唤醒机制

2.12 [1]synchronized:偏向锁

01.偏向锁
    a.定义
        偏向锁是一种优化锁机制,旨在减少无竞争情况下的同步开销
        它假设大多数情况下,锁是由同一个线程多次获得的,因此在没有竞争的情况下,偏向锁可以避免频繁的CAS操作
    b.原理
        a.核心思想
            如果一个线程获得了锁,那么锁就进入偏向模式,锁会偏向于这个线程
            此时,线程在进入和退出同步块时不需要执行同步操作(如CAS操作),只需简单地检查对象头中的标志位即可
        b.获取偏向锁
            当一个线程第一次获取锁时,JVM 会将该线程的ID记录在对象头的Mark Word中,并将锁标志位设置为偏向锁状态
            此后,该线程再次进入同步块时,只需检查对象头中的线程ID是否与当前线程ID匹配即可
        c.撤销偏向锁
            如果另一个线程尝试获取已经偏向于某个线程的锁,偏向锁会被撤销
            JVM 会暂停持有偏向锁的线程,检查其是否仍然处于锁定状态
            如果是,则将锁升级为轻量级锁或重量级锁;如果不是,则将锁状态恢复为无锁状态
    c.常用API
        a.介绍
            偏向锁是JVM内部的优化机制,开发者不需要直接使用API来操作偏向锁
        b.偏向锁的启用和禁用可以通过JVM参数进行配置
            启用偏向锁(默认启用):-XX:+UseBiasedLocking
            禁用偏向锁:-XX:-UseBiasedLocking
            延迟偏向锁:-XX:BiasedLockingStartupDelay=0(设置偏向锁的启动延迟时间,默认是4秒)
    d.详情
        a.介绍
            偏向锁的实现依赖于对象头中的Mark Word。Mark Word是对象头的一部分,用于存储对象的运行时数据,
            包括锁状态、哈希码、GC信息等。偏向锁通过在Mark Word中记录线程ID来实现
        b.偏向锁的状态转换
            无锁状态:对象头的Mark Word中没有记录任何线程ID
            偏向锁状态:对象头的Mark Word中记录了持有锁的线程ID
            轻量级锁状态:偏向锁被撤销,锁升级为轻量级锁
            重量级锁状态:轻量级锁竞争激烈,锁升级为重量级锁
        c.偏向锁的撤销过程
            检查对象头中的线程ID是否与当前线程ID匹配
            如果不匹配,暂停持有偏向锁的线程
            检查持有偏向锁的线程是否仍然处于锁定状态
            如果是,将锁升级为轻量级锁或重量级锁;如果不是,将锁状态恢复为无锁状态

02.偏向锁
    a.应用场景
        a.无竞争的同步块
            在大多数情况下,锁是由同一个线程多次获得的,且没有其他线程竞争锁
        b.读多写少的场景
            在读多写少的场景中,偏向锁可以减少无竞争情况下的同步开销,提高性能
        c.单线程访问的场景
            在单线程访问的场景中,偏向锁可以避免频繁的CAS操作,提高性能
    b.代码示例
        a.代码
            public class BiasedLockExample {
                private int count = 0;

                public synchronized void increment() {
                    count++;
                }

                public int getCount() {
                    return count;
                }

                public static void main(String[] args) {
                    BiasedLockExample example = new BiasedLockExample();

                    // 启动一个线程进行操作,触发偏向锁
                    Thread t1 = new Thread(() -> {
                        for (int j = 0; j < 1000; j++) {
                            example.increment();
                        }
                    });
                    t1.start();

                    // 等待线程完成
                    try {
                        t1.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    // 打印最终的计数值
                    System.out.println("Final count: " + example.getCount());
                }
            }
        b.说明
            在这个示例中,increment 方法被 synchronized 修饰,表示它是一个同步方法
            当 t1 线程第一次调用 increment 方法时,锁进入偏向锁状态,锁会偏向于 t1 线程
            此后,t1 线程再次进入 increment 方法时,只需检查对象头中的线程ID是否与当前线程ID匹配即可,无需执行同步操作

2.13 [1]synchronized:轻量级锁,自旋优化

01.自适应自旋
    a.概念
        a.自旋锁
            当一个线程尝试获取锁而失败时,它可以选择不立即进入阻塞状态,
            而是进行一段时间的忙等待(自旋),以期望锁在短时间内被释放
            自旋期间,线程会不断地检查锁是否可用
        b.自适应自旋
            自适应自旋是对自旋锁的改进
            自适应自旋的时间不是固定的,而是由JVM根据线程的历史行为和系统的运行状况动态调整
            这样可以更有效地利用CPU资源,减少线程上下文切换的开销
    b.工作原理
        a.初始自旋
            当一个线程第一次尝试获取锁而失败时,JVM会让线程自旋一段时间
            自旋的时间长度是根据系统的默认设置或JVM的配置参数决定的
        b.动态调整
            如果自旋成功(即在自旋期间锁被释放并成功获取),JVM会在下次自旋时增加自旋时间,因为这表明自旋是有效的
        c.自旋失败
            如果自旋失败(即自旋期间锁未被释放),JVM会减少下次自旋的时间,甚至直接让线程进入阻塞状态
        d.历史行为
            JVM会根据线程的历史行为(如之前自旋的成功率)和系统的负载情况来动态调整自旋时间
    c.优势
        减少阻塞:通过自旋,线程可以避免进入阻塞状态,从而减少线程上下文切换的开销
        提高性能:在锁竞争不激烈的情况下,自适应自旋可以显著提高系统的并发性能
    d.适用场景
        自适应自旋适用于锁竞争不激烈且锁持有时间较短的场景
        在这种情况下,自旋可以有效减少线程阻塞和上下文切换的开销

02.synchronized自旋机制
    a.回答
        自适应自旋是JVM内部的优化机制,开发者通常不需要直接控制它
    b.说明
        synchronized 的自旋机制是JVM的一种优化策略
        通过在【轻量级锁阶段】使用【自旋】来【减少线程阻塞和上下文切换的开销】,提高系统的并发性能
    c.示例
        public class SpinLockExample {
            private volatile boolean isLocked = false;

            public void lock() {
                while (!compareAndSet(false, true)) {
                    // 自旋等待
                }
            }

            public void unlock() {
                isLocked = false;
            }

            private boolean compareAndSet(boolean expected, boolean newValue) {
                if (isLocked == expected) {
                    isLocked = newValue;
                    return true;
                }
                return false;
            }
        }
        -----------------------------------------------------------------------------------------------------
        lock() 方法使用自旋等待来获取锁,虽然这不是 synchronized 的实现,但它展示了自旋锁的基本思想

2.14 [1]synchronized:重量级锁,不会降级

01.锁的状态变化
    a.介绍
        synchronized 锁在Java中有多种状态,包括无锁状态、偏向锁、轻量级锁和重量级锁
        当竞争激烈时,锁会逐步升级,最终可能升级到重量级锁
    b.四种状态
        无锁状态:没有线程竞争,锁处于无锁状态
        偏向锁:当一个线程第一次获取锁时,锁会偏向该线程,后续该线程再次获取锁时无需进行同步操作
        轻量级锁:当多个线程竞争锁时,锁会升级为轻量级锁,使用自旋锁来避免线程阻塞
        重量级锁:当自旋锁无法解决竞争问题时,锁会升级为重量级锁,使用操作系统的互斥量(mutex)来实现线程同步
    c.锁不会降级的原因
        锁升级之后不会降级,这是为了避免频繁的锁状态转换带来的性能开销。
        锁的降级会涉及复杂的状态转换和同步操作,可能会导致更多的性能问题。
        因此,Java设计中选择了锁升级后不降级的策略。

02.锁的常见问题
    a.线程阻塞和唤醒
        重量级锁使用操作系统的互斥量来实现同步,这意味着当一个线程获取锁时,其他竞争锁的线程会被阻塞
        进入等待队列。被阻塞的线程会被挂起,直到持有锁的线程释放锁,操作系统会唤醒等待队列中的一个或多个线程
    b.性能影响
        重量级锁的使用会导致线程上下文切换和操作系统的调度开销,性能会显著下降
        线程上下文切换涉及保存和恢复线程的执行状态,这是一项昂贵的操作
    c.内存一致性
        重量级锁会确保内存的一致性,即在锁释放之前,所有对共享变量的修改对其他线程可见
        这是通过内存屏障(memory barrier)实现的,确保指令的顺序和内存的可见性
    d.锁的释放
        当持有锁的线程执行完同步代码块或方法后,会释放锁
        操作系统会从等待队列中选择一个线程并唤醒它,使其获取锁并继续执行

03.代码示例
    a.代码
        public class HeavyLockExample {
            private int count = 0;

            public synchronized void increment() {
                count++;
            }

            public synchronized int getCount() {
                return count;
            }

            public static void main(String[] args) {
                HeavyLockExample example = new HeavyLockExample();

                Runnable task = () -> {
                    for (int i = 0; i < 10000; i++) {
                        example.increment();
                    }
                };

                Thread thread1 = new Thread(task);
                Thread thread2 = new Thread(task);
                Thread thread3 = new Thread(task);

                thread1.start();
                thread2.start();
                thread3.start();

                try {
                    thread1.join();
                    thread2.join();
                    thread3.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("Final count: " + example.getCount());
            }
        }
    b.说明
        多个线程同时竞争 increment() 方法的锁,可能会导致锁升级为重量级锁,从而引起线程阻塞和上下文切换

2.15 [1]synchronized:锁字符串,不建议

00.总结
    虽然可以使用字符串作为锁对象,但由于字符串常量池的特性和不可变性,通常不建议这样做
    使用专用的锁对象可以避免潜在的锁竞争和意外行为

01.定义
    在Java中,synchronized关键字可以用于锁定任何对象,包括字符串对象
    然而,锁定字符串对象通常不是一个好的实践

02.原因
    a.字符串常量池
        Java中的字符串具有字符串常量池(String Intern Pool)的特性。相同的字符串字面量在内存中只会存在一个实例
        如果你使用"someString"作为锁对象,所有使用相同字面量的代码块都会共享同一个锁。这可能导致意外的锁竞争和性能问题
    b.不可变性
        字符串是不可变的,这意味着一旦创建,字符串对象的值就不能改变
        这种不可变性使得字符串在多线程环境中是线程安全的,但也意味着锁定字符串对象可能会导致意外的行为

03.建议
    a.使用私有锁对象
        如果需要锁定某个资源,建议使用一个私有的、专用的锁对象,而不是字符串
        这样可以避免与其他代码块意外共享锁
    b.使用私有锁对象的示例
        public class SafeLockExample {
            // 使用私有的锁对象
            private final Object lock = new Object();

            public void safeMethod() {
                synchronized (lock) {
                    // 线程安全的代码块
                    System.out.println("This is a thread-safe operation.");
                }
            }
        }

2.16 [1]synchronized:代码如果抛出异常,锁自动释放

00.汇总
    synchronized代码块或方法的代码如果抛出异常,锁会自动释放

01.说明
    这是因为Java 的 synchronized 关键字是基于 JVM 的监视器锁(Monitor Lock)机制实现的
    当一个线程进入 synchronized 代码块或方法时,它会获取该对象的 monitor 锁
    当线程离开 synchronized 代码块或方法时,它会释放 monitor 锁
    JVM 会确保在 synchronized 代码块或方法执行结束后(无论是正常结束还是异常结束),锁都会被正确释放
    这种机制避免了因异常导致的死锁问题,确保了锁的可靠释放

02.示例
    a.代码
        private static final Object lock = new Object();
        private static int counter = 0;

        public static void main(String[] args) {
            Thread thread1 = new Thread(() -> {
                try {
                    incrementAndThrow();
                } catch (RuntimeException e) {
                    System.out.println(Thread.currentThread().getName() + " 捕获到异常: " + e.getMessage());
                }
            }, "Thread 1");

            Thread thread2 = new Thread(() -> {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + " 获取到锁,计数器值: " + counter);
                }
            }, "Thread 2");

            thread1.start();
            // 稍微延迟一下,确保 thread1 先执行
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            thread2.start();
        }

        private static void incrementAndThrow() {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + " 获取到锁,增加计数器");
                counter++;
                throw new RuntimeException("故意抛出异常");
            }
        }
    b.输出
        Thread 1 获取到锁,增加计数器
        Thread 1 捕获到异常: 故意抛出异常
        Thread 2 获取到锁,计数器值: 1
    c.说明
        从输出结果可以看出,即使 thread1 在持有锁的情况下抛出了异常,thread2 仍然能够获取到锁,并访问 counter 变量
        这证明了 synchronized 代码块在抛出异常时会释放锁
        counter 的值为 1 也证明了 thread1 在抛出异常之前成功执行了 counter++ 操作

2.17 [2]ReentrantLock:可重入锁,公平锁/非公平锁(默认)

00.ReentrantLock
    a.定义
        显式锁,需要手动获取和释放锁,支持可重入,提供了更多高级功能(如公平锁、可中断锁、条件变量)
    b.特点
        可重入性:同一线程可以多次获取同一把锁,而不会发生死锁
        公平性:可以选择公平锁,按照请求锁的顺序分配锁,避免线程饥饿
        中断响应:支持在等待锁时响应中断
        条件变量:支持通过 Condition 对象实现更复杂的等待/通知机制。
    c.API
        a.加锁
            lock(): 显式加锁
            lockInterruptibly(): 可中断的加锁
        b.解锁
            unlock(): 显式解锁,必须在try-finally块中使用,以确保锁的释放
        c.条件变量
            newCondition(): 创建一个条件变量,用于实现等待/通知机制
            Condition.await(): 使当前线程等待,直到被唤醒
            Condition.signal(): 唤醒一个等待的线程
            Condition.signalAll(): 唤醒所有等待的线程
        d.查询状态
            isHeldByCurrentThread(): 判断当前线程是否持有锁
            getHoldCount(): 获取当前线程持有锁的次数
    d.代码示例
        a.基本使用
            // 1. 创建ReentrantLock对象
            ReentrantLock lock = new ReentrantLock();
            // 2.获取锁
            lock.lock();
            try {
                // 3.得到锁,执行需要同步的代码块
            } finally {
                // 4.释放锁
                lock.unlock();
            }
        b.尝试获取锁并设定超时时间(可选)
            ReentrantLock lock = new ReentrantLock();
            // 尝试获取锁,等待2秒,超时返回false
            boolean locked = lock.tryLock(2, TimeUnit.SECONDS);
            if (locked) {
                try {
                    // 执行需要同步的代码块
                } finally {
                    lock.unlock();
                }
            }

01.公平锁(Fair Lock)
    a.定义
        公平锁是指按照请求锁的顺序来分配锁,确保先请求锁的线程优先获得锁,避免线程饥饿(即某些线程长时间无法获得锁)
    b.实现
        在创建 ReentrantLock 时,可以通过构造函数参数指定为公平锁
        ReentrantLock fairLock = new ReentrantLock(true); // 公平锁
    c.优点
        确保线程按照请求的顺序获取锁,避免某些线程长时间等待
    d.缺点
        由于公平性,可能导致性能下降,尤其在高竞争的环境中,因为每个线程都必须等待前面的线程释放锁。
    e.代码
        import java.util.concurrent.locks.ReentrantLock;

        public class FairLockExample {
            private static final ReentrantLock fairLock = new ReentrantLock(true); // 创建公平锁

            public void fairLockMethod() {
                fairLock.lock(); // 获取锁
                try {
                    // 执行临界区代码
                    System.out.println(Thread.currentThread().getName() + " acquired the fair lock.");
                    Thread.sleep(100); // 模拟一些工作
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    fairLock.unlock(); // 释放锁
                }
            }

            public static void main(String[] args) {
                FairLockExample example = new FairLockExample();

                // 创建多个线程来测试公平锁
                for (int i = 0; i < 5; i++) {
                    new Thread(example::fairLockMethod, "Thread-" + i).start();
                }
            }
        }

02.非公平锁(Unfair Lock)(默认)
    a.定义
        非公平锁是指不保证请求锁的顺序,允许线程“插队”,即后请求锁的线程可能会先获得锁
    b.实现
        默认情况下,ReentrantLock 是非公平锁
        ReentrantLock nonFairLock = new ReentrantLock(); // 非公平锁
    c.优点
        提高了吞吐量,尤其在高并发场景中,因为线程可以更快地获取锁,减少了上下文切换的开销。
    d.缺点
        可能导致某些线程长时间无法获得锁,出现线程饥饿的情况。
    e.代码
        import java.util.concurrent.locks.ReentrantLock;

        public class NonFairLockExample {
            private static final ReentrantLock nonFairLock = new ReentrantLock(); // 创建非公平锁

            public void nonFairLockMethod() {
                nonFairLock.lock(); // 获取锁
                try {
                    // 执行临界区代码
                    System.out.println(Thread.currentThread().getName() + " acquired the non-fair lock.");
                    Thread.sleep(100); // 模拟一些工作
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    nonFairLock.unlock(); // 释放锁
                }
            }

            public static void main(String[] args) {
                NonFairLockExample example = new NonFairLockExample();

                // 创建多个线程来测试非公平锁
                for (int i = 0; i < 5; i++) {
                    new Thread(example::nonFairLockMethod, "Thread-" + i).start();
                }
            }
        }

2.18 [3]ReentrantReadWriteLock:读写锁,读锁/写锁

00.ReentrantReadWriteLock
    a.定义
        一种读写锁,允许多个读线程并发访问,但写线程在写入时会独占锁,从而防止其他线程的读写操作
    b.特点
        读锁是共享的,允许多个读线程并发访问
        写锁是独占的,确保写操作的原子性和一致性
        支持可重入性
    c.两种模式
        读锁:共享锁,允许多个线程同时持有,只要没有写线程持有写锁
        写锁:独占锁,一次只能有一个线程持有,且持有时不允许读锁
    d.API
        a.加锁
            读锁:readLock().lock()
            写锁:writeLock().lock()
        b.解锁
            读锁:readLock().unlock()
            写锁:writeLock().unlock()
        c.等待与唤醒
            使用Condition接口与ReentrantLock相同
            可以为读锁和写锁分别创建条件变量

01.读锁(Read Lock)
    a.特点
        允许多个线程同时获取读锁,只要没有写锁被持有
        读锁是共享的,多个读线程可以并发访问共享资源
    b.获取读锁
        使用 readLock().lock() 方法获取读锁
    c.释放读锁
        使用 readLock().unlock() 方法释放读锁
    d.示例
        ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();

        public void readData() {
            readLock.lock(); // 获取读锁
            try {
                // 执行读取操作
            } finally {
                readLock.unlock(); // 释放读锁
            }
        }

02.写锁(Write Lock)
    a.特点
        写锁是独占的,只有一个写线程可以获取写锁
        在写锁被持有时,其他线程(包括读线程和写线程)都不能获取锁
    b.获取写锁
        使用 writeLock().lock() 方法获取写锁
    c.释放读锁
        使用 writeLock().unlock() 方法释放写锁
    d.示例
        ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();

        public void writeData() {
            writeLock.lock(); // 获取写锁
            try {
                // 执行写入操作
            } finally {
                writeLock.unlock(); // 释放写锁
            }
        }

03.锁降级
    a.介绍
        读写锁,支持【锁降级】,不支持【锁升级】
        遵循按照获取写锁,获取读锁再释放写锁的次序,写锁能够降级成为读锁
    b.流程
        1.获取读锁:首先尝试获取读锁来检查某个缓存是否有效
        2.检查缓存:如果缓存无效,则需要释放读锁,因为在获取写锁之前必须释放读锁
        3.获取写锁:获取写锁以便更新缓存。此时,可能还需要重新检查缓存状态,因为在释放读锁和获取写锁之间可能有其他线程修改了状态
        4.更新缓存:如果确认缓存无效,更新缓存并将其标记为有效
        5.写锁降级为读锁:在释放写锁之前,获取读锁,从而实现写锁到读锁的降级。这样,在释放写锁后,其他线程可以并发读取,但不能写入
        6.使用数据:现在可以安全地使用缓存数据了
        7.释放读锁:完成操作后释放读锁

2.19 [4]StampedLock:时间戳锁,读锁/写锁/乐观读锁,JDK8引入

00.StampedLock:时间戳锁
    a.定义
        一种改进的读写锁,提供更高的性能和灵活性
    b.三种模式
        a.读锁
            readLock,共享模式,允许多个线程同时读
        b.写锁
            writeLock,独占模式,其他线程无法读或写
        c.乐观读锁
            tryOptimisticRead,一种非阻塞的读操作,适合短时间内【读多写少】的场景
            乐观读锁获取后,在读取共享变量前发生写操作,则validate方法返回false,需转换为【悲观读锁】、【写锁】重新访问共享变量
    c.API
        a.加锁
            悲观写锁:long stamp = writeLock()
            悲观读锁:long stamp = readLock()
            乐观读锁:long stamp = tryOptimisticRead()
        b.解锁
            悲观写锁:unlockWrite(stamp)
            悲观读锁:unlockRead(stamp)
            乐观读锁:validate(stamp)(检查乐观锁是否有效)
        c.等待与唤醒
            StampedLock本身不提供等待和唤醒机制,通常与Condition结合使用

01.写锁(Write Lock)
    a.定义
        写锁是独占的,只有一个线程可以获取写锁,其他线程(包括读线程和写线程)在写锁被持有时无法获取锁。
    b.获取和释放写锁
        使用 writeLock() 方法获取写锁
        使用 unlockWrite(stamp) 方法释放写锁
    c.示例
        import java.util.concurrent.locks.StampedLock;

        public class StampedLockExample {
            private final StampedLock stampedLock = new StampedLock();
            private int value = 0;

            public void writeValue(int newValue) {
                long stamp = stampedLock.writeLock(); // 获取写锁
                try {
                    value = newValue; // 执行写入操作
                } finally {
                    stampedLock.unlockWrite(stamp); // 释放写锁
                }
            }
        }

02.读锁(Read Lock)
    a.定义
        读锁允许多个线程同时获取锁,以便并发读取共享资源。
    b.获取和释放读锁
        使用 readLock() 方法获取读锁。
        使用 unlockRead(stamp) 方法释放读锁。
    c.示例
        import java.util.concurrent.locks.StampedLock;

        public class StampedLockExample {
            private final StampedLock stampedLock = new StampedLock();
            private int value = 0;

            public int readValue() {
                long stamp = stampedLock.readLock(); // 获取读锁
                try {
                    return value; // 执行读取操作
                } finally {
                    stampedLock.unlockRead(stamp); // 释放读锁
                }
            }
        }

03.乐观读锁
    a.定义
        乐观锁允许线程在不获取锁的情况下读取数据,假设在读取期间不会发生写操作。
        在执行写操作时,线程需要验证在读取期间数据是否被修改。
    b.获取和验证乐观锁
        使用 tryOptimisticRead() 方法获取乐观读锁。
        使用 validate(stamp) 方法验证乐观锁是否有效。
    c.示例
        import java.util.concurrent.locks.StampedLock;

        public class StampedLockExample {
            private final StampedLock stampedLock = new StampedLock();
            private int value = 0;

            public int optimisticReadValue() {
                long stamp = stampedLock.tryOptimisticRead(); // 获取乐观读锁
                int currentValue = value; // 执行读取操作

                // 验证乐观锁是否有效
                if (!stampedLock.validate(stamp)) {
                    // 如果乐观锁无效,重新获取读锁
                    stamp = stampedLock.readLock();
                    try {
                        currentValue = value; // 再次读取
                    } finally {
                        stampedLock.unlockRead(stamp); // 释放读锁
                    }
                }
                return currentValue;
            }
        }

3 乐观锁

3.1 定义:读少写多

01.乐观锁:写多读少
    正好和悲观锁相反,它获取数据的时候,并不担心数据被修改,每次获取数据的时候也不会加锁
    只是在更新数据的时候,通过判断现有的数据是否和原数据一致来判断数据是否被其他线程操作
    如果没被其他线程修改则进行数据更新,如果被其他线程修改则不进行数据更新

3.2 实现:3种

01.方式1:版本号
    a.介绍
        通过在数据表中添加一个版本号字段,每次更新数据时,检查版本号是否与当前版本匹配
        如果匹配,则更新数据并增加版本号;如果不匹配,则说明数据已被其他事务修改,需要重试或抛出异常
    b.示例:使用版本号进行乐观锁控制
        -- 假设有一个表 `items`,包含 `id`、`name` 和 `version` 列
        UPDATE items
        SET name = 'newName', version = version + 1
        WHERE id = ? AND version = ?;
    c.Java中
        可以使用JDBC或ORM框架(如Hibernate)来实现这种乐观锁机制

02.方式2:CAS(原子操作)/AQS(框架,使用CAS)
    a.两种同步模式:【独占模式】和【共享模式】
        a.ReentrantLock
            a.介绍
                ReentrantLock 是基于 AQS 的一个可重入锁,支持独占模式
            b.独占模式
                tryAcquire(): 判断锁是否被占用
                tryRelease(): 释放锁
        b.Semaphore
            a.定义
                Semaphore 是基于 AQS 的一个信号量实现,支持共享模式
            b.共享模式
                tryAcquireShared(): 判断是否还有剩余许可
                tryReleaseShared(): 释放许可
        c.CountDownLatch
            a.定义
                CountDownLatch 是基于 AQS 的一个同步工具类,支持共享模式
            b.共享模式
                tryAcquireShared(): 判断计数器是否为 0
    b.ConcurrentHashMap
        a.介绍
            ConcurrentHashMap 是 Java 中的一个线程安全的哈希表实现
            使用了 CAS(Compare-And-Swap)技术来实现高效的并发访问
        b.说明
            通过 CAS 操作来确保对某个桶的更新是原子的,从而避免锁的使用,提高并发性能
    c.StampedLock
        a.介绍
            StampedLock 是 Java 8 引入的一种锁机制,提供了乐观读锁的支持
            它允许在不加锁的情况下读取数据,并在需要时升级为悲观锁
        b.说明
            StampedLock 使用 CAS 操作来实现乐观读锁的验证和升级
    d.java.util.concurrent.atomic包
        a.介绍
            提供了一系列原子类
            AtomicInteger、AtomicLong、AtomicBoolean、AtomicReference、AtomicStampedReference
            使用 CAS 操作来实现线程安全的更新
        b.LongAdder和DoubleAdder
            Java8引入的用于高并发场景下的计数器,使用分段锁和CAS技术来减少竞争,提高性能
    e.其他
        a.Unsafe类
            提供了底层的CAS操作,许多并发工具类(如 Atomic 类)都是基于 Unsafe 类的 CAS 操作实现的
        b.LockSupport
            用于线程阻塞和唤醒的工具类,虽然不直接使用CAS,但其实现依赖于底层的原子操作
        c.CompletableFuture
            使用 CAS 操作来实现任务的完成和结果的设置,确保线程安全的异步计算
        d.ForkJoinPool
            使用 CAS 操作来管理工作窃取队列和任务的执行状态

03.方式3:Redis Watch+Spring Retry重试机制

3.3 [1]版本号

00.定义
    通过在数据表中添加一个版本号字段,每次更新数据时,检查版本号是否与当前版本匹配
    如果匹配,则更新数据并增加版本号;如果不匹配,则说明数据已被其他事务修改,需要重试或抛出异常

01.Java中实现版本号
    a.代码
        public class Item {
            private int id;
            private String name;
            private int version;

            // Getters and setters

            public boolean updateItem(String newName, int expectedVersion) {
                synchronized (this) {
                    if (this.version == expectedVersion) {
                        this.name = newName;
                        this.version++;
                        return true;
                    } else {
                        return false;
                    }
                }
            }
        }
    b.说明
        updateItem方法使用同步块来确保线程安全,并在更新前检查版本号

02.MySQL中的版本号实现
    a.代码
        -- 更新数据时检查版本号
        UPDATE items
        SET name = 'newName', version = version + 1
        WHERE id = ? AND version = ?;
    b.说明
        只有当 id 和 version 匹配时,才会更新 name 和 version
        如果版本号不匹配,说明数据已被其他事务修改,更新操作将不会影响任何行

03.使用JDBC或ORM框架
    a.代码
        import java.sql.Connection;
        import java.sql.PreparedStatement;
        import java.sql.SQLException;

        public class ItemDAO {
            private Connection connection;

            public ItemDAO(Connection connection) {
                this.connection = connection;
            }

            public boolean updateItem(int id, String newName, int expectedVersion) throws SQLException {
                String sql = "UPDATE items SET name = ?, version = version + 1 WHERE id = ? AND version = ?";
                try (PreparedStatement stmt = connection.prepareStatement(sql)) {
                    stmt.setString(1, newName);
                    stmt.setInt(2, id);
                    stmt.setInt(3, expectedVersion);
                    int rowsAffected = stmt.executeUpdate();
                    return rowsAffected > 0;
                }
            }
        }
    b.说明
        updateItem方法使用JDBC执行SQL更新语句,并根据返回的受影响行数判断更新是否成功

3.4 [2]AQS使用CAS:Atomic类

00.Atomic类
    a.基本类型
        AtomicBoolean                 布尔值
        AtomicInteger                 整数
        AtomicLong                    长整数
    b.数组类型
        AtomicIntegerArray            整数数组的元素
        AtomicLongArray               长整数数组的元素
        AtomicReferenceArray          对象引用数组的元素
    c.引用类型
        AtomicMarkableReference       对象引用和标记,解决【ABA问题】
        AtomicStampedReference        对象引用和标记,解决【ABA问题】
        AtomicReference               对象引用
    d.属性更新类型
        AtomicIntegerFieldUpdater     对象的int字段
        AtomicLongFieldUpdater        对象的long字段
        AtomicReferenceFieldUpdater   对象的引用字段
    e.累加器
        DoubleAccumulator             双精度浮点数,高并发环境
        DoubleAdder                   双精度浮点数,高并发环境
        LongAccumulator               长整数,高并发环境
        LongAdder                     长整数,高并发环境

01.基本类型
    a.AtomicBoolean
        a.定义
            用于对布尔值进行原子操作
        b.原理
            通过 CAS 操作确保布尔值的原子性更新
        c.常用 API
            get(): 获取当前值
            set(boolean newValue): 设置新值
            compareAndSet(boolean expect, boolean update): 如果当前值等于 expect,则更新为 update
        d.使用步骤
            1.创建 AtomicBoolean 实例
            2.使用 get()、set() 或 compareAndSet() 方法进行操作
        e.示例
            import java.util.concurrent.atomic.AtomicBoolean;

            public class AtomicBooleanExample {
                private final AtomicBoolean flag = new AtomicBoolean(false);

                public void toggle() {
                    boolean currentValue;
                    do {
                        currentValue = flag.get();
                    } while (!flag.compareAndSet(currentValue, !currentValue));
                }

                public boolean getFlag() {
                    return flag.get();
                }

                public static void main(String[] args) {
                    AtomicBooleanExample example = new AtomicBooleanExample();
                    example.toggle();
                    System.out.println("Flag: " + example.getFlag());
                }
            }
    b.AtomicInteger
        a.定义
            用于对整数进行原子操作
        b.原理
            通过 CAS 操作确保整数的原子性更新
        c.常用 API
            get(): 获取当前值
            set(int newValue): 设置新值
            incrementAndGet(): 原子性地增加 1
            decrementAndGet(): 原子性地减少 1
            compareAndSet(int expect, int update): 如果当前值等于 expect,则更新为 update
        d.使用步骤
            1.创建 AtomicInteger 实例
            2.使用 get()、set()、incrementAndGet()、decrementAndGet() 或 compareAndSet() 方法进行操作
        e.示例
            import java.util.concurrent.atomic.AtomicInteger;

            public class AtomicIntegerExample {
                private final AtomicInteger counter = new AtomicInteger(0);

                public void increment() {
                    counter.incrementAndGet();
                }

                public int getCounter() {
                    return counter.get();
                }

                public static void main(String[] args) {
                    AtomicIntegerExample example = new AtomicIntegerExample();
                    example.increment();
                    System.out.println("Counter: " + example.getCounter());
                }
            }
    c.AtomicLong
        a.定义
            用于对长整数进行原子操作
        b.原理
            通过 CAS 操作确保长整数的原子性更新
        c.常用 API
            get(): 获取当前值
            set(long newValue): 设置新值
            incrementAndGet(): 原子性地增加 1
            decrementAndGet(): 原子性地减少 1
            compareAndSet(long expect, long update): 如果当前值等于 expect,则更新为 update
        d.使用步骤
            1.创建 AtomicLong 实例
            2.使用 get()、set()、incrementAndGet()、decrementAndGet() 或 compareAndSet() 方法进行操作
        e.示例
            import java.util.concurrent.atomic.AtomicLong;

            public class AtomicLongExample {
                private final AtomicLong counter = new AtomicLong(0);

                public void increment() {
                    counter.incrementAndGet();
                }

                public long getCounter() {
                    return counter.get();
                }

                public static void main(String[] args) {
                    AtomicLongExample example = new AtomicLongExample();
                    example.increment();
                    System.out.println("Counter: " + example.getCounter());
                }
            }

02.数组类型
    a.AtomicIntegerArray
        a.定义
            用于对整数数组的元素进行原子操作
        b.原理
            通过 CAS 操作确保数组元素的原子性更新
        c.常用 API
            get(int i): 获取索引 i 处的值
            set(int i, int newValue): 设置索引 i 处的新值
            compareAndSet(int i, int expect, int update): 如果索引 i 处的当前值等于 expect,则更新为 update
        d.使用步骤
            1.创建 AtomicIntegerArray 实例
            2.使用 get()、set() 或 compareAndSet() 方法进行操作
        e.示例
            import java.util.concurrent.atomic.AtomicIntegerArray;

            public class AtomicIntegerArrayExample {
                private final AtomicIntegerArray array = new AtomicIntegerArray(5);

                public void increment(int index) {
                    array.incrementAndGet(index);
                }

                public int getValue(int index) {
                    return array.get(index);
                }

                public static void main(String[] args) {
                    AtomicIntegerArrayExample example = new AtomicIntegerArrayExample();
                    example.increment(0);
                    System.out.println("Value at index 0: " + example.getValue(0));
                }
            }
    b.AtomicLongArray
        a.定义
            用于对长整数数组的元素进行原子操作
        b.原理
            通过 CAS 操作确保数组元素的原子性更新
        c.常用 API
            get(int i): 获取索引 i 处的值
            set(int i, long newValue): 设置索引 i 处的新值
            compareAndSet(int i, long expect, long update): 如果索引 i 处的当前值等于 expect,则更新为 update
        d.使用步骤
            1.创建 AtomicLongArray 实例。
            2.使用 get()、set() 或 compareAndSet() 方法进行操作。
        e.示例
            import java.util.concurrent.atomic.AtomicLongArray;

            public class AtomicLongArrayExample {
                private final AtomicLongArray array = new AtomicLongArray(5);

                public void increment(int index) {
                    array.incrementAndGet(index);
                }

                public long getValue(int index) {
                    return array.get(index);
                }

                public static void main(String[] args) {
                    AtomicLongArrayExample example = new AtomicLongArrayExample();
                    example.increment(0);
                    System.out.println("Value at index 0: " + example.getValue(0));
                }
            }
    c.AtomicReferenceArray
        a.定义
            用于对对象引用数组的元素进行原子操作
        b.原理
            通过 CAS 操作确保数组元素的原子性更新
        c.常用 API
            get(int i): 获取索引 i 处的值
            set(int i, V newValue): 设置索引 i 处的新值
            compareAndSet(int i, V expect, V update): 如果索引 i 处的当前值等于 expect,则更新为 update
        d.使用步骤
            1.创建 AtomicReferenceArray 实例
            2.使用 get()、set() 或 compareAndSet() 方法进行操作
        e.示例
            ---
            import java.util.concurrent.atomic.AtomicReferenceArray;

            public class AtomicReferenceArrayExample {
                private final AtomicReferenceArray<String> array = new AtomicReferenceArray<>(5);

                public void setValue(int index, String value) {
                    array.set(index, value);
                }

                public String getValue(int index) {
                    return array.get(index);
                }

                public static void main(String[] args) {
                    AtomicReferenceArrayExample example = new AtomicReferenceArrayExample();
                    example.setValue(0, "Hello");
                    System.out.println("Value at index 0: " + example.getValue(0));
                }
            }

03.引用类型
    a.AtomicMarkableReference
        a.定义
            用于对对象引用和标记进行原子操作
        b.原理
            通过 CAS 操作确保对象引用和标记的原子性更新
        c.常用 API
            getReference(): 获取当前引用
            isMarked(): 获取当前标记
            compareAndSet(V expectedReference, V newReference, boolean expectedMark, boolean newMark): 如果当前引用和标记分别等于 expectedReference 和 expectedMark,则更新为 newReference 和 newMark
        d.使用步骤
            1.创建 AtomicMarkableReference 实例
            2.使用 getReference()、isMarked() 或 compareAndSet() 方法进行操作
        e.示例
            import java.util.concurrent.atomic.AtomicMarkableReference;

            public class AtomicMarkableReferenceExample {
                private final AtomicMarkableReference<String> ref = new AtomicMarkableReference<>("Initial", false);

                public void update(String newValue, boolean newMark) {
                    String currentRef;
                    boolean currentMark;
                    do {
                        currentRef = ref.getReference();
                        currentMark = ref.isMarked();
                    } while (!ref.compareAndSet(currentRef, newValue, currentMark, newMark));
                }

                public String getReference() {
                    return ref.getReference();
                }

                public boolean isMarked() {
                    return ref.isMarked();
                }

                public static void main(String[] args) {
                    AtomicMarkableReferenceExample example = new AtomicMarkableReferenceExample();
                    example.update("Updated", true);
                    System.out.println("Reference: " + example.getReference() + ", Marked: " + example.isMarked());
                }
            }
    b.AtomicStampedReference
        a.定义
            用于对对象引用和标记进行原子操作,解决了 ABA 问题
        b.原理
            通过 CAS 操作确保对象引用和标记的原子性更新
        c.常用 API
            getReference(): 获取当前引用
            getStamp(): 获取当前标记
            compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp): 如果当前引用和标记分别等于 expectedReference 和 expectedStamp,则更新为 newReference 和 newStamp
        d.使用步骤
            1.创建 AtomicStampedReference 实例
            2.使用 getReference()、getStamp() 或 compareAndSet() 方法进行操作
        e.示例
            import java.util.concurrent.atomic.AtomicStampedReference;

            public class AtomicStampedReferenceExample {
                private final AtomicStampedReference<String> ref = new AtomicStampedReference<>("Initial", 0);

                public void update(String newValue, int newStamp) {
                    String currentRef;
                    int currentStamp;
                    do {
                        currentRef = ref.getReference();
                        currentStamp = ref.getStamp();
                    } while (!ref.compareAndSet(currentRef, newValue, currentStamp, newStamp));
                }

                public String getReference() {
                    return ref.getReference();
                }

                public int getStamp() {
                    return ref.getStamp();
                }

                public static void main(String[] args) {
                    AtomicStampedReferenceExample example = new AtomicStampedReferenceExample();
                    example.update("Updated", 1);
                    System.out.println("Reference: " + example.getReference() + ", Stamp: " + example.getStamp());
                }
            }
    c.AtomicReference
        a.定义
            用于对对象引用进行原子操作
        b.原理
            通过 CAS 操作确保对象引用的原子性更新
        c.常用 API
            get(): 获取当前引用
            set(V newValue): 设置新引用
            compareAndSet(V expect, V update): 如果当前引用等于 expect,则更新为 update
        d.使用步骤
            1.创建 AtomicReference 实例
            2.使用 get()、set() 或 compareAndSet() 方法进行操作
        e.示例
            import java.util.concurrent.atomic.AtomicReference;

            public class AtomicReferenceExample {
                private final AtomicReference<String> ref = new AtomicReference<>("Initial");

                public void update(String newValue) {
                    String currentRef;
                    do {
                        currentRef = ref.get();
                    } while (!ref.compareAndSet(currentRef, newValue));
                }

                public String getReference() {
                    return ref.get();
                }

                public static void main(String[] args) {
                    AtomicReferenceExample example = new AtomicReferenceExample();
                    example.update("Updated");
                    System.out.println("Reference: " + example.getReference());
                }
            }

04.属性更新类型
    a.AtomicIntegerFieldUpdater
        a.定义
            用于对对象的 int 字段进行原子更新
        b.原理
            通过反射和 CAS 操作确保字段的原子性更新
        c.常用 API
            newUpdater(Class<T> tclass, String fieldName): 创建一个更新器
            get(T obj): 获取字段值
            set(T obj, int newValue): 设置字段值
            compareAndSet(T obj, int expect, int update): 如果字段值等于 expect,则更新为 update
        d.使用步骤
            1.创建 AtomicIntegerFieldUpdater 实例
            2.使用 get()、set() 或 compareAndSet() 方法进行操作
        e.示例
            import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

            public class AtomicIntegerFieldUpdaterExample {
                private static class Data {
                    volatile int value;
                }

                private final AtomicIntegerFieldUpdater<Data> updater =
                    AtomicIntegerFieldUpdater.newUpdater(Data.class, "value");

                public void update(Data data, int newValue) {
                    updater.set(data, newValue);
                }

                public int getValue(Data data) {
                    return updater.get(data);
                }

                public static void main(String[] args) {
                    AtomicIntegerFieldUpdaterExample example = new AtomicIntegerFieldUpdaterExample();
                    Data data = new Data();
                    example.update(data, 42);
                    System.out.println("Value: " + example.getValue(data));
                }
            }
    b.AtomicLongFieldUpdater
        a.定义
            用于对对象的 long 字段进行原子更新
        b.原理
            通过反射和 CAS 操作确保字段的原子性更新
        c.常用 API
            newUpdater(Class<T> tclass, String fieldName): 创建一个更新器
            get(T obj): 获取字段值
            set(T obj, long newValue): 设置字段值
            compareAndSet(T obj, long expect, long update): 如果字段值等于 expect,则更新为 update
        d.使用步骤
            1.创建 AtomicLongFieldUpdater 实例
            2.使用 get()、set() 或 compareAndSet() 方法进行操作
        e.示例
            import java.util.concurrent.atomic.AtomicLongFieldUpdater;

            public class AtomicLongFieldUpdaterExample {
                private static class Data {
                    volatile long value;
                }

                private final AtomicLongFieldUpdater<Data> updater =
                    AtomicLongFieldUpdater.newUpdater(Data.class, "value");

                public void update(Data data, long newValue) {
                    updater.set(data, newValue);
                }

                public long getValue(Data data) {
                    return updater.get(data);
                }

                public static void main(String[] args) {
                    AtomicLongFieldUpdaterExample example = new AtomicLongFieldUpdaterExample();
                    Data data = new Data();
                    example.update(data, 42L);
                    System.out.println("Value: " + example.getValue(data));
                }
            }
    c.AtomicReferenceFieldUpdater
        a.定义
            用于对对象的引用字段进行原子更新
        b.原理
            通过反射和 CAS 操作确保字段的原子性更新
        c.常用 API
            newUpdater(Class<T> tclass, Class<V> vclass, String fieldName): 创建一个更新器
            get(T obj): 获取字段值
            set(T obj, V newValue): 设置字段值
            compareAndSet(T obj, V expect, V update): 如果字段值等于 expect,则更新为 update
        d.使用步骤
            1.创建 AtomicReferenceFieldUpdater 实例
            2.使用 get()、set() 或 compareAndSet() 方法进行操作
        e.示例
            import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

            public class AtomicReferenceFieldUpdaterExample {
                private static class Data {
                    volatile String value;
                }

                private final AtomicReferenceFieldUpdater<Data, String> updater =
                    AtomicReferenceFieldUpdater.newUpdater(Data.class, String.class, "value");

                public void update(Data data, String newValue) {
                    updater.set(data, newValue);
                }

                public String getValue(Data data) {
                    return updater.get(data);
                }

                public static void main(String[] args) {
                    AtomicReferenceFieldUpdaterExample example = new AtomicReferenceFieldUpdaterExample();
                    Data data = new Data();
                    example.update(data, "Hello");
                    System.out.println("Value: " + example.getValue(data));
                }
            }

05.累加器
    a.DoubleAccumulator
        a.定义
            用于在高并发环境下执行双精度浮点数的累加操作
        b.原理
            通过分段锁和 CAS 操作减少竞争,提高性能
        c.常用 API
            accumulate(double x): 执行累加操作
            get(): 获取当前累加值
            reset(): 重置累加值
        d.使用步骤
            1.创建 DoubleAccumulator 实例
            2.使用 accumulate() 方法进行累加
            3.使用 get() 方法获取累加结果
        e.示例
            import java.util.concurrent.atomic.DoubleAccumulator;
            import java.util.function.DoubleBinaryOperator;

            public class DoubleAccumulatorExample {
                private final DoubleAccumulator accumulator =
                    new DoubleAccumulator(Double::sum, 0.0);

                public void add(double value) {
                    accumulator.accumulate(value);
                }

                public double getTotal() {
                    return accumulator.get();
                }

                public static void main(String[] args) {
                    DoubleAccumulatorExample example = new DoubleAccumulatorExample();
                    example.add(1.5);
                    example.add(2.5);
                    System.out.println("Total: " + example.getTotal());
                }
            }
    b.DoubleAdder
        a.定义
            用于在高并发环境下执行双精度浮点数的累加操作
        b.原理
            通过分段锁和 CAS 操作减少竞争,提高性能
        c.常用 API
            add(double x): 执行累加操作
            sum(): 获取当前累加值
            reset(): 重置累加值
        d.使用步骤
            1.创建 DoubleAdder 实例
            2.使用 add() 方法进行累加
            3.使用 sum() 方法获取累加结果
        e.示例
            import java.util.concurrent.atomic.DoubleAdder;

            public class DoubleAdderExample {
                private final DoubleAdder adder = new DoubleAdder();

                public void add(double value) {
                    adder.add(value);
                }

                public double getTotal() {
                    return adder.sum();
                }

                public static void main(String[] args) {
                    DoubleAdderExample example = new DoubleAdderExample();
                    example.add(1.5);
                    example.add(2.5);
                    System.out.println("Total: " + example.getTotal());
                }
            }
    c.LongAccumulator
        a.定义
            用于在高并发环境下执行长整数的累加操作
        b.原理
            通过分段锁和 CAS 操作减少竞争,提高性能
        c.常用 API
            accumulate(long x): 执行累加操作
            get(): 获取当前累加值
            reset(): 重置累加值
        d.使用步骤
            1.创建 LongAccumulator 实例
            2.使用 accumulate() 方法进行累加
            3.使用 get() 方法获取累加结果
        e.示例
            import java.util.concurrent.atomic.LongAccumulator;
            import java.util.function.LongBinaryOperator;

            public class LongAccumulatorExample {
                private final LongAccumulator accumulator =
                    new LongAccumulator(Long::sum, 0);

                public void add(long value) {
                    accumulator.accumulate(value);
                }

                public long getTotal() {
                    return accumulator.get();
                }

                public static void main(String[] args) {
                    LongAccumulatorExample example = new LongAccumulatorExample();
                    example.add(1);
                    example.add(2);
                    System.out.println("Total: " + example.getTotal());
                }
            }
    d.LongAdder
        a.定义
            用于在高并发环境下执行长整数的累加操作
        b.原理
            通过分段锁和 CAS 操作减少竞争,提高性能
        c.常用 API
            add(long x): 执行累加操作
            sum(): 获取当前累加值
            reset(): 重置累加值
        d.使用步骤
            1.创建 LongAdder 实例
            2.使用 add() 方法进行累加
            3.使用 sum() 方法获取累加结果
        e.示例
            import java.util.concurrent.atomic.LongAdder;

            public class LongAdderExample {
                private final LongAdder adder = new LongAdder();

                public void add(long value) {
                    adder.add(value);
                }

                public long getTotal() {
                    return adder.sum();
                }

                public static void main(String[] args) {
                    LongAdderExample example = new LongAdderExample();
                    example.add(1);
                    example.add(2);
                    System.out.println("Total: " + example.getTotal());
                }
            }

3.5 [3]Redis Watch+Spring Retry重试机制

01.Redis Watch
    a.原理
        Redis 提供了 WATCH 命令,用于监控一个或多个键的变化
        在执行 MULTI 命令之前调用 WATCH,如果在事务执行期间这些键发生了变化,事务将被中止
    b.使用步骤
        1.使用 WATCH 命令监控需要保护的键
        2.开始事务(MULTI)
        3.执行一系列命令
        4.提交事务(EXEC),如果在此期间监控的键发生了变化,EXEC 将返回 null,表示事务失败

02.Spring Retry
    a.原理
        Spring Retry 提供了重试机制,可以在操作失败时自动重试,直到成功或达到最大重试次数
    b.使用步骤
        添加 Spring Retry 依赖
        配置重试策略(如固定延迟、指数退避等)
        使用 @Retryable 注解标记需要重试的方法

03.结合使用:Redis Watch + Spring Retry 实现 乐观锁
    a.实现步骤
        监控键:在操作开始前,使用 Redis 的 WATCH 命令监控需要保护的键
        开始事务:使用 MULTI 开始事务
        执行操作:在事务中执行需要的操作
        提交事务:使用 EXEC 提交事务。如果事务失败(返回 null),则抛出异常
        重试机制:使用 Spring Retry 的 @Retryable 注解标记执行操作的方法,以便在事务失败时自动重试
    b.代码示例
        import org.springframework.retry.annotation.Backoff;
        import org.springframework.retry.annotation.Retryable;
        import org.springframework.stereotype.Service;
        import redis.clients.jedis.Jedis;

        @Service
        public class OptimisticLockService {

            private final Jedis jedis;

            public OptimisticLockService(Jedis jedis) {
                this.jedis = jedis;
            }

            @Retryable(value = { OptimisticLockException.class }, maxAttempts = 5, backoff = @Backoff(delay = 1000))
            public void performOptimisticLockOperation(String key, String newValue) {
                jedis.watch(key);
                String currentValue = jedis.get(key);

                // 开始事务
                jedis.multi();
                jedis.set(key, newValue);

                // 提交事务
                if (jedis.exec() == null) {
                    throw new OptimisticLockException("Transaction failed, retrying...");
                }
            }
        }

        class OptimisticLockException extends RuntimeException {
            public OptimisticLockException(String message) {
                super(message);
            }
        }
    c.代码说明
        重试策略:根据业务需求配置合适的重试策略,避免过多的重试导致系统负载过高
        事务粒度:确保事务内的操作尽可能小,以减少冲突的可能性
        异常处理:在重试达到最大次数后,确保有适当的异常处理机制

3.6 AQS框架:独占锁、共享锁

01.AQS概念
    a.同步状态(State)
        AQS通过一个volatile类型的int变量state来表示同步状态。子类通过继承AQS并实现其方法来操作这个状态
        常见的操作包括获取和释放同步状态
    b.FIFO等待队列
        AQS使用一个FIFO等待队列来管理被阻塞的线程。每个节点(Node)表示一个线程,节点之间通过前驱和后继指针连接
        当线程获取同步状态失败时,会被加入到等待队列中,直到同步状态可用
    c.模式
        独占锁:只有一个线程能独占同步状态(如独占锁)
        共享锁:多个线程可以共享同步状态(如信号量)

02.AQS的原理
    a.获取同步状态
        独占模式:通过acquire(int arg)方法尝试获取同步状态。如果获取失败,线程会被加入等待队列并阻塞,直到同步状态可用
        共享模式:通过acquireShared(int arg)方法尝试获取同步状态。如果获取失败,线程会被加入等待队列并阻塞,直到同步状态可用
    b.释放同步状态
        独占模式:通过release(int arg)方法释放同步状态。如果释放成功,会唤醒等待队列中的下一个线程
        共享模式:通过releaseShared(int arg)方法释放同步状态。如果释放成功,会唤醒等待队列中的所有线程
    c.等待队列
        AQS使用一个双向链表来实现等待队列。每个节点(Node)表示一个线程,包含线程引用、前驱和后继指针、等待状态等信息
        当线程获取同步状态失败时,会被加入到等待队列的尾部,并进入阻塞状态
    d.唤醒机制
        当同步状态被释放时,AQS会唤醒等待队列中的一个或多个线程,使其重新尝试获取同步状态

03.常用API
    getState(): 获取当前同步状态
    setState(int newState): 设置同步状态
    compareAndSetState(int expect, int update): 原子地设置同步状态
    acquire(int arg): 独占模式下获取资源
    release(int arg): 独占模式下释放资源
    acquireShared(int arg): 共享模式下获取资源
    releaseShared(int arg): 共享模式下释放资源
    hasQueuedThreads(): 判断是否有线程在等待队列中
    isHeldExclusively(): 判断当前线程是否独占资源

04.使用步骤
    1.创建一个继承自AQS的类
    2.实现tryAcquire、tryRelease、tryAcquireShared和tryReleaseShared方法
    3.使用acquire和release方法来获取和释放资源

05.应用场景
    a.独占锁
        import java.util.concurrent.locks.AbstractQueuedSynchronizer;

        public class Mutex {
            private static class Sync extends AbstractQueuedSynchronizer {
                @Override
                protected boolean tryAcquire(int arg) {
                    if (compareAndSetState(0, 1)) {
                        setExclusiveOwnerThread(Thread.currentThread());
                        return true;
                    }
                    return false;
                }

                @Override
                protected boolean tryRelease(int arg) {
                    if (getState() == 0) {
                        throw new IllegalMonitorStateException();
                    }
                    setExclusiveOwnerThread(null);
                    setState(0);
                    return true;
                }

                @Override
                protected boolean isHeldExclusively() {
                    return getState() == 1;
                }
            }

            private final Sync sync = new Sync();

            public void lock() {
                sync.acquire(1);
            }

            public void unlock() {
                sync.release(1);
            }
        }

        public class MutexExample {
            public static void main(String[] args) {
                Mutex mutex = new Mutex();
                mutex.lock();
                try {
                    // 临界区代码
                } finally {
                    mutex.unlock();
                }
            }
        }
    b.共享锁
        import java.util.concurrent.locks.AbstractQueuedSynchronizer;

        public class SharedLock {
            private static class Sync extends AbstractQueuedSynchronizer {
                private final int maxCount;

                public Sync(int maxCount) {
                    this.maxCount = maxCount;
                }

                @Override
                protected int tryAcquireShared(int arg) {
                    for (;;) {
                        int current = getState();
                        int newCount = current + arg;
                        if (newCount > maxCount || compareAndSetState(current, newCount)) {
                            return newCount <= maxCount ? 1 : -1;
                        }
                    }
                }

                @Override
                protected boolean tryReleaseShared(int arg) {
                    for (;;) {
                        int current = getState();
                        int newCount = current - arg;
                        if (compareAndSetState(current, newCount)) {
                            return true;
                        }
                    }
                }
            }

            private final Sync sync;

            public SharedLock(int maxCount) {
                sync = new Sync(maxCount);
            }

            public void lock() {
                sync.acquireShared(1);
            }

            public void unlock() {
                sync.releaseShared(1);
            }
        }

        public class SharedLockExample {
            public static void main(String[] args) {
                SharedLock sharedLock = new SharedLock(3);
                sharedLock.lock();
                try {
                    // 临界区代码
                } finally {
                    sharedLock.unlock();
                }
            }
        }
    c.条件变量
        import java.util.concurrent.locks.AbstractQueuedSynchronizer;
        import java.util.concurrent.locks.Condition;

        public class ConditionLock {
            private static class Sync extends AbstractQueuedSynchronizer {
                @Override
                protected boolean tryAcquire(int arg) {
                    if (compareAndSetState(0, 1)) {
                        setExclusiveOwnerThread(Thread.currentThread());
                        return true;
                    }
                    return false;
                }

                @Override
                protected boolean tryRelease(int arg) {
                    if (getState() == 0) {
                        throw new IllegalMonitorStateException();
                    }
                    setExclusiveOwnerThread(null);
                    setState(0);
                    return true;
                }

                @Override
                protected boolean isHeldExclusively() {
                    return getState() == 1;
                }

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

            private final Sync sync = new Sync();

            public void lock() {
                sync.acquire(1);
            }

            public void unlock() {
                sync.release(1);
            }

            public Condition newCondition() {
                return sync.newCondition();
            }
        }

        public class ConditionLockExample {
            public static void main(String[] args) throws InterruptedException {
                ConditionLock lock = new ConditionLock();
                Condition condition = lock.newCondition();

                Thread t1 = new Thread(() -> {
                    lock.lock();
                    try {
                        System.out.println("Thread 1 waiting");
                        condition.await();
                        System.out.println("Thread 1 resumed");
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    } finally {
                        lock.unlock();
                    }
                });

                Thread t2 = new Thread(() -> {
                    lock.lock();
                    try {
                        System.out.println("Thread 2 signaling");
                        condition.signal();
                    } finally {
                        lock.unlock();
                    }
                });

                t1.start();
                Thread.sleep(1000); // 确保t1先执行
                t2.start();

                t1.join();
                t2.join();
            }
        }

3.7 CAS原子:无锁算法

01.定义
    CAS(Compare-And-Swap)是一种原子操作,用于实现无锁并发。它通过比较和交换来确保数据的原子性更新

02.原理
    CAS操作包含三个操作数:内存位置(V)、预期值(A)和新值(B)
    当且仅当内存位置的当前值等于预期值时,CAS操作才会将内存位置的值更新为新值
    否则,不会执行任何操作。CAS操作通常由硬件指令支持,以确保其原子性

03.常用API
    compareAndSet(expectedValue, newValue): 如果当前值等于预期值,则将其更新为新值
    get(): 获取当前值
    set(newValue): 设置新值

04.使用步骤
    1.创建一个支持CAS操作的变量(如AtomicInteger)
    2.使用compareAndSet方法进行原子更新
    3.使用get方法获取当前值,使用set方法设置新值

05.应用场景
    a.使用AtomicInteger进行CAS操作
        import java.util.concurrent.atomic.AtomicInteger;

        public class CASExample {
            private final AtomicInteger value = new AtomicInteger(0);

            public void increment() {
                int oldValue;
                int newValue;
                do {
                    oldValue = value.get();
                    newValue = oldValue + 1;
                } while (!value.compareAndSet(oldValue, newValue));
            }

            public int getValue() {
                return value.get();
            }

            public static void main(String[] args) {
                CASExample example = new CASExample();
                example.increment();
                System.out.println("Value: " + example.getValue());
            }
        }
    b.使用AtomicReference进行CAS操作
        import java.util.concurrent.atomic.AtomicReference;

        public class CASReferenceExample {
            private final AtomicReference<String> value = new AtomicReference<>("Initial");

            public void update(String newValue) {
                String oldValue;
                do {
                    oldValue = value.get();
                } while (!value.compareAndSet(oldValue, newValue));
            }

            public String getValue() {
                return value.get();
            }

            public static void main(String[] args) {
                CASReferenceExample example = new CASReferenceExample();
                example.update("Updated");
                System.out.println("Value: " + example.getValue());
            }
        }
    c.使用AtomicStampedReference解决ABA问题
        import java.util.concurrent.atomic.AtomicStampedReference;

        public class CASStampedReferenceExample {
            private final AtomicStampedReference<String> value = new AtomicStampedReference<>("Initial", 0);

            public void update(String newValue) {
                int[] stampHolder = new int[1];
                String oldValue;
                int oldStamp;
                do {
                    oldValue = value.get(stampHolder);
                    oldStamp = stampHolder[0];
                } while (!value.compareAndSet(oldValue, newValue, oldStamp, oldStamp + 1));
            }

            public String getValue() {
                return value.getReference();
            }

            public int getStamp() {
                return value.getStamp();
            }

            public static void main(String[] args) {
                CASStampedReferenceExample example = new CASStampedReferenceExample();
                example.update("Updated");
                System.out.println("Value: " + example.getValue() + ", Stamp: " + example.getStamp());
            }
        }
    d.使用AtomicMarkableReference进行CAS操作
        import java.util.concurrent.atomic.AtomicMarkableReference;

        public class CASMarkableReferenceExample {
            private final AtomicMarkableReference<String> value = new AtomicMarkableReference<>("Initial", false);

            public void update(String newValue, boolean newMark) {
                String oldValue;
                boolean oldMark;
                do {
                    oldValue = value.getReference();
                    oldMark = value.isMarked();
                } while (!value.compareAndSet(oldValue, newValue, oldMark, newMark));
            }

            public String getValue() {
                return value.getReference();
            }

            public boolean isMarked() {
                return value.isMarked();
            }

            public static void main(String[] args) {
                CASMarkableReferenceExample example = new CASMarkableReferenceExample();
                example.update("Updated", true);
                System.out.println("Value: " + example.getValue() + ", Marked: " + example.isMarked());
            }
        }

3.8 CAS原子:三大问题

00.CAS的三大问题
    1.ABA问题:可以通过 AtomicStampedReference 解决
    2.长时间自旋:在高并发环境下需要注意自旋次数和性能问题
    3.多个共享变量的原子操作:可以通过封装多个变量为一个对象,使用 AtomicReference 进行原子操作

01.ABA问题
    a.定义
        ABA问题是指一个变量从 A 变为 B,然后又变回 A
        CAS 操作无法检测到这种变化,认为变量没有变化,从而导致数据不一致的问题
    b.原理
        CAS 操作只检查变量的当前值是否等于预期值,而不检查变量是否经历了其他变化
        因此,如果一个变量从 A 变为 B,然后又变回 A,CAS 操作会认为变量没有变化,导致 ABA 问题
    c.常用 API
        AtomicStampedReference:通过使用标记(stamp)来解决 ABA 问题
    d.使用步骤
        1.创建 AtomicStampedReference 实例
        2.使用 compareAndSet 方法进行原子操作,检查标记是否变化
    e.代码示例
        import java.util.concurrent.atomic.AtomicStampedReference;

        public class ABAExample {
            public static void main(String[] args) {
                AtomicStampedReference<Integer> atomicRef = new AtomicStampedReference<>(100, 0);

                int[] stampHolder = new int[1];
                Integer value = atomicRef.get(stampHolder);
                int stamp = stampHolder[0];

                // Simulate ABA problem
                atomicRef.compareAndSet(value, 101, stamp, stamp + 1);
                atomicRef.compareAndSet(101, 100, stamp + 1, stamp + 2);

                // Attempt to update with original stamp
                boolean success = atomicRef.compareAndSet(100, 200, stamp, stamp + 1);
                System.out.println("Update success: " + success); // Output: false
            }
        }

02.长时间自旋
    a.定义
        长时间自旋是指在高并发环境下,CAS 操作可能会长时间自旋,消耗大量 CPU 资源,导致性能下降的问题
    b.原理
        CAS 操作在失败时会不断重试,直到成功
        在高并发环境下,如果竞争激烈,CAS 操作可能会长时间自旋,消耗大量 CPU 资源
    c.常用 API
        AtomicInteger、AtomicLong 等原子类
    d.使用步骤
        1.使用原子类进行 CAS 操作
        2.在高并发环境下,注意自旋次数和性能问题
    e.代码示例
        import java.util.concurrent.atomic.AtomicInteger;

        public class LongSpinExample {
            private final AtomicInteger counter = new AtomicInteger(0);

            public void increment() {
                int oldValue;
                int newValue;
                do {
                    oldValue = counter.get();
                    newValue = oldValue + 1;
                } while (!counter.compareAndSet(oldValue, newValue));
            }

            public int getCounter() {
                return counter.get();
            }

            public static void main(String[] args) {
                LongSpinExample example = new LongSpinExample();
                for (int i = 0; i < 1000; i++) {
                    new Thread(example::increment).start();
                }
                System.out.println("Counter: " + example.getCounter());
            }
        }

03.多个共享变量的原子操作
    a.定义
        多个共享变量的原子操作是指在某些场景下,需要对多个共享变量进行原子操作
        而 CAS 只能对单个变量进行原子操作,无法保证多个变量的原子性
    b.原理
        CAS 操作只能对单个变量进行原子操作,无法保证多个变量的原子性
        因此,在需要对多个变量进行原子操作时,CAS 无法提供线程安全的保证
    c.常用 API
        AtomicReference:通过封装多个变量为一个对象,使用 AtomicReference 进行原子操作
    d.使用步骤
        1.创建包含多个变量的对象
        2.使用 AtomicReference 封装该对象
        3.使用 compareAndSet 方法进行原子操作
    e.代码示例
        import java.util.concurrent.atomic.AtomicReference;

        public class MultiVariableExample {
            private static class Pair {
                final int x;
                final int y;

                Pair(int x, int y) {
                    this.x = x;
                    this.y = y;
                }
            }

            private final AtomicReference<Pair> pairRef = new AtomicReference<>(new Pair(0, 0));

            public void update(int newX, int newY) {
                Pair oldPair;
                Pair newPair;
                do {
                    oldPair = pairRef.get();
                    newPair = new Pair(newX, newY);
                } while (!pairRef.compareAndSet(oldPair, newPair));
            }

            public Pair getPair() {
                return pairRef.get();
            }

            public static void main(String[] args) {
                MultiVariableExample example = new MultiVariableExample();
                example.update(1, 1);
                Pair pair = example.getPair();
                System.out.println("Pair: (" + pair.x + ", " + pair.y + ")");
            }
        }

3.9 ABA问题:版本号、AtomicStampedReference或AtomicMarkableReference

00.回答
    1.版本号
    2.AtomicStampedReference、AtomicMarkableReference

01.定义
    a.介绍
        ABA问题发生在一个线程在检查某个变量的值时,发现变量的值没有变化,
        但实际上该变量的值在此期间可能已经被其他线程修改过,并且又改回了原来的值。
        这种情况下,检查变量的线程认为变量的值没有变化,但实际上变量的状态已经发生了变化。
    b.示例
        假设有两个线程在操作一个共享变量V:
        1.初始状态:线程1读取变量V的值,发现V = A
        2.中间状态:线程2将变量V的值从A修改为B,然后又将B修改回A
        3.最终状态:线程1再次检查变量V的值,发现V仍然是A,认为变量的值没有变化,但实际上变量的状态已经发生了变化。
    c.影响
        1.错误判断:由于线程1认为变量的值没有变化,可能会导致错误的判断和操作
        2.数据不一致:ABA问题可能导致数据不一致,特别是在使用CAS操作进行无锁编程时

02.原理
    ABA问题的根源在于CAS操作只检查变量的值是否与预期值相等,而不检查变量在此期间是否经历过其他变化
    即使变量的值最终与预期值相同,但中间可能经历了多次变化,这些变化可能会影响程序的正确性。=

03.常用API
    AtomicStampedReference: 通过增加一个版本号(时间戳)来解决ABA问题
    AtomicMarkableReference: 通过增加一个标记位来解决ABA问题

04.解决ABA问题
    1.在进行CAS操作时,使用【版本号】来确保变量没有经历过其他变化
    2.使用【AtomicStampedReference】、【AtomicMarkableReference】来代替普通的原子类

05.应用场景
    a.AtomicStampedReference解决ABA问题
        import java.util.concurrent.atomic.AtomicStampedReference;

        public class ABAStampedReferenceExample {
            private final AtomicStampedReference<String> value = new AtomicStampedReference<>("Initial", 0);

            public void update(String newValue) {
                int[] stampHolder = new int[1];
                String oldValue;
                int oldStamp;
                do {
                    oldValue = value.get(stampHolder);
                    oldStamp = stampHolder[0];
                } while (!value.compareAndSet(oldValue, newValue, oldStamp, oldStamp + 1));
            }

            public String getValue() {
                return value.getReference();
            }

            public int getStamp() {
                return value.getStamp();
            }

            public static void main(String[] args) {
                ABAStampedReferenceExample example = new ABAStampedReferenceExample();
                example.update("Updated");
                System.out.println("Value: " + example.getValue() + ", Stamp: " + example.getStamp());
            }
        }
        -----------------------------------------------------------------------------------------------------
        1.创建一个AtomicStampedReference实例
        2.在进行CAS操作时,使用compareAndSet方法,并传入预期的引用和版本号
        3.在每次更新变量时,增加版本号
    b.AtomicMarkableReference解决ABA问题
        import java.util.concurrent.atomic.AtomicMarkableReference;

        public class ABAMarkableReferenceExample {
            private final AtomicMarkableReference<String> value = new AtomicMarkableReference<>("Initial", false);

            public void update(String newValue, boolean newMark) {
                String oldValue;
                boolean oldMark;
                do {
                    oldValue = value.getReference();
                    oldMark = value.isMarked();
                } while (!value.compareAndSet(oldValue, newValue, oldMark, newMark));
            }

            public String getValue() {
                return value.getReference();
            }

            public boolean isMarked() {
                return value.isMarked();
            }

            public static void main(String[] args) {
                ABAMarkableReferenceExample example = new ABAMarkableReferenceExample();
                example.update("Updated", true);
                System.out.println("Value: " + example.getValue() + ", Marked: " + example.isMarked());
            }
        }

3.10 AQS、CAS、ABA

01.回答
    AQS:一个框架,用于构建锁和同步器,支持多种同步模式
    CAS:一种原子操作,用于实现无锁并发编程,可能会遇到ABA问题
    ABA问题:CAS操作中的一个常见问题,可以通过版本号或AtomicStampedReference来解决

01.AQS(AbstractQueuedSynchronizer)
    a.定义
        AQS 是 Java 并发包中的一个基础框架,用于构建锁和同步器。它提供了独占模式和共享模式两种同步模式
    b.原理
        AQS 使用一个 FIFO 队列来管理线程的排队和唤醒
        独占模式:一个线程持有锁,其他线程等待
        共享模式:多个线程可以同时访问资源
    c.优点
        灵活:支持多种同步器的实现,如 ReentrantLock、Semaphore、CountDownLatch
        可扩展:开发者可以通过继承 AQS 来实现自定义同步器
    d.缺点
        复杂性:实现和使用 AQS 需要理解其内部机制

02.CAS(Compare-And-Swap)
    a.定义
        CAS 是一种无锁的原子操作,用于实现线程安全的更新。它通过比较和交换操作来确保数据的原子性
    b.原理
        CAS 操作涉及三个操作数:内存位置(V)、预期值(A)和新值(B)
        如果内存位置的当前值等于预期值,则将其更新为新值
        CAS 操作是原子的,通常由硬件支持
    c.优点
        无锁:避免了传统锁的开销,提高了并发性能
        高效:适用于多线程环境下的简单更新操作
    d.缺点
        ABA 问题:如果一个值从 A 变为 B,然后又变回 A,CAS 操作可能无法检测到这种变化

03.ABA 问题
    a.定义
        ABA 问题是 CAS 操作中的一个常见问题。当一个值从 A 变为 B,然后又变回 A,CAS 操作可能无法检测到这种变化
    b.原理
        ABA 问题发生在 CAS 操作中,因为 CAS 只检查值是否等于预期值,而不检查值是否经历了其他变化
    c.解决方案
        使用版本号:通过附加一个版本号来跟踪值的变化
        AtomicStampedReference:Java 提供了 AtomicStampedReference 类,通过使用标记(stamp)来解决 ABA 问题

3.11 AQS、AQLS、AOS

00.汇总
    AQS(AbstractQueuedSynchronizer):用于构建锁和同步器,提供独占模式和共享模式两种同步模式
    AQLS(AbstractQueuedLongSynchronizer):基于长整数(long)的锁和同步器,类似于 AQS,但使用 long 类型的状态变量
    AOS(AbstractOwnableSynchronizer):支持独占模式的同步器,提供对当前持有锁的线程的管理

01.AQS(AbstractQueuedSynchronizer)
    a.定义
        AQS 是 Java 并发包中的一个基础框架,用于构建锁和同步器。它提供了独占模式和共享模式两种同步模式
    b.原理
        AQS 使用一个 FIFO 队列来管理线程的排队和唤醒。线程在获取锁失败时,会被加入到等待队列中,等待其他线程释放锁后被唤醒
    c.常用 API
        acquire(int arg): 独占模式下获取锁
        release(int arg): 独占模式下释放锁
        acquireShared(int arg): 共享模式下获取锁
        releaseShared(int arg): 共享模式下释放锁
        tryAcquire(int arg): 尝试获取锁(独占模式)
        tryRelease(int arg): 尝试释放锁(独占模式)
        tryAcquireShared(int arg): 尝试获取锁(共享模式)
        tryReleaseShared(int arg): 尝试释放锁(共享模式)
    d.使用步骤
        1.创建一个继承自 AQS 的类
        2.实现 tryAcquire、tryRelease 等方法
        3.使用自定义的同步器类来管理锁和同步
    e.代码示例
        import java.util.concurrent.locks.AbstractQueuedSynchronizer;

        public class CustomLock {
            private static class Sync extends AbstractQueuedSynchronizer {
                @Override
                protected boolean tryAcquire(int arg) {
                    if (compareAndSetState(0, 1)) {
                        setExclusiveOwnerThread(Thread.currentThread());
                        return true;
                    }
                    return false;
                }

                @Override
                protected boolean tryRelease(int arg) {
                    if (getState() == 0) throw new IllegalMonitorStateException();
                    setExclusiveOwnerThread(null);
                    setState(0);
                    return true;
                }
            }

            private final Sync sync = new Sync();

            public void lock() {
                sync.acquire(1);
            }

            public void unlock() {
                sync.release(1);
            }

            public static void main(String[] args) {
                CustomLock lock = new CustomLock();
                lock.lock();
                try {
                    System.out.println("Lock acquired");
                } finally {
                    lock.unlock();
                }
            }
        }

02.AQLS(AbstractQueuedLongSynchronizer)
    a.定义
        AQLS 是 Java 并发包中的一个基础框架,用于构建基于长整数(long)的锁和同步器
        它是 AQS 的一个变种,使用 long 类型的状态变量
    b.原理
        AQLS 使用一个 FIFO 队列来管理线程的排队和唤醒
        线程在获取锁失败时,会被加入到等待队列中,等待其他线程释放锁后被唤醒
    c.常用 API
        acquire(long arg): 独占模式下获取锁
        release(long arg): 独占模式下释放锁
        acquireShared(long arg): 共享模式下获取锁
        releaseShared(long arg): 共享模式下释放锁
        tryAcquire(long arg): 尝试获取锁(独占模式)
        tryRelease(long arg): 尝试释放锁(独占模式)
        tryAcquireShared(long arg): 尝试获取锁(共享模式)
        tryReleaseShared(long arg): 尝试释放锁(共享模式)
    d.使用步骤
        1.创建一个继承自 AQLS 的类
        2.实现 tryAcquire、tryRelease 等方法
        3.使用自定义的同步器类来管理锁和同步
    e.代码示例
        import java.util.concurrent.locks.AbstractQueuedLongSynchronizer;

        public class CustomLongLock {
            private static class Sync extends AbstractQueuedLongSynchronizer {
                @Override
                protected boolean tryAcquire(long arg) {
                    if (compareAndSetState(0, 1)) {
                        setExclusiveOwnerThread(Thread.currentThread());
                        return true;
                    }
                    return false;
                }

                @Override
                protected boolean tryRelease(long arg) {
                    if (getState() == 0) throw new IllegalMonitorStateException();
                    setExclusiveOwnerThread(null);
                    setState(0);
                    return true;
                }
            }

            private final Sync sync = new Sync();

            public void lock() {
                sync.acquire(1);
            }

            public void unlock() {
                sync.release(1);
            }

            public static void main(String[] args) {
                CustomLongLock lock = new CustomLongLock();
                lock.lock();
                try {
                    System.out.println("Lock acquired");
                } finally {
                    lock.unlock();
                }
            }
        }

03.AOS(AbstractOwnableSynchronizer)
    a.定义
        AOS 是 Java 并发包中的一个基础类,用于支持独占模式的同步器
        它提供了对当前持有锁的线程的管理
    b.原理
        AOS 提供了对当前持有锁的线程的管理
        通过 getExclusiveOwnerThread 和 setExclusiveOwnerThread 方法来设置和获取当前持有锁的线程
    c.常用 API
        getExclusiveOwnerThread(): 获取当前持有锁的线程
        setExclusiveOwnerThread(Thread thread): 设置当前持有锁的线程
    d.使用步骤
        1.创建一个继承自 AOS 的类
        2.使用 getExclusiveOwnerThread 和 setExclusiveOwnerThread 方法来管理持有锁的线程
    e.代码示例
        import java.util.concurrent.locks.AbstractOwnableSynchronizer;

        public class CustomOwnableLock {
            private static class Sync extends AbstractOwnableSynchronizer {
                private boolean isLocked = false;

                protected synchronized boolean tryAcquire() {
                    if (!isLocked) {
                        isLocked = true;
                        setExclusiveOwnerThread(Thread.currentThread());
                        return true;
                    }
                    return false;
                }

                protected synchronized boolean tryRelease() {
                    if (getExclusiveOwnerThread() == Thread.currentThread()) {
                        isLocked = false;
                        setExclusiveOwnerThread(null);
                        return true;
                    }
                    return false;
                }
            }

            private final Sync sync = new Sync();

            public void lock() {
                while (!sync.tryAcquire()) {
                    // Busy-wait
                }
            }

            public void unlock() {
                sync.tryRelease();
            }

            public static void main(String[] args) {
                CustomOwnableLock lock = new CustomOwnableLock();
                lock.lock();
                try {
                    System.out.println("Lock acquired");
                } finally {
                    lock.unlock();
                }
            }
        }

4 通信工具类

4.1 [1]CountDownLatch:倒计时锁存器,线程等待直到计数器减为0时开始工作

01.定义
    CountDownLatch 是一个同步辅助类,用于协调多个线程之间的执行顺序
    它通过一个计数器来实现,该计数器初始化为一个正数,表示需要等待的事件数量
    每当一个事件完成时,计数器减一。当计数器达到零时,所有等待的线程被释放

02.原理
    CountDownLatch 的工作原理基于一个计数器
    主线程可以在某个点调用 await() 方法,进入等待状态,直到计数器变为零
    其他线程在完成各自的任务后调用 countDown() 方法,减少计数器的值
    当计数器变为零时,所有调用 await() 的线程将被唤醒并继续执行

03.常用API
    a.构造函数
        CountDownLatch(int count): 创建一个 CountDownLatch,初始化计数器为指定的 count
    b.方法
        void await(): 使当前线程等待,直到计数器变为零
        boolean await(long timeout, TimeUnit unit): 使当前线程等待,直到计数器变为零或超时
        void countDown(): 递减计数器的值
        long getCount(): 返回当前计数器的值

04.使用步骤
    a.创建CountDownLatch
        指定需要等待的事件数量
    b.启动线程
        在每个线程中执行任务,并在任务完成后调用 countDown()
    c.等待完成
        在主线程中调用 await(),等待所有任务完成

05.应用场景及代码示例
    a.主线程等待多个线程完成各自的任务
        import java.util.concurrent.CountDownLatch;

        public class CountDownLatchExample {
            public static void main(String[] args) throws InterruptedException {
                int numberOfThreads = 3;
                CountDownLatch latch = new CountDownLatch(numberOfThreads);

                for (int i = 0; i < numberOfThreads; i++) {
                    new Thread(new Worker(latch)).start();
                }

                latch.await(); // Wait for all threads to complete
                System.out.println("All threads have finished their tasks.");
            }
        }

        class Worker implements Runnable {
            private final CountDownLatch latch;

            public Worker(CountDownLatch latch) {
                this.latch = latch;
            }

            @Override
            public void run() {
                try {
                    // Simulate work
                    Thread.sleep((long) (Math.random() * 1000));
                    System.out.println(Thread.currentThread().getName() + " finished work.");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown(); // Decrement the count
                }
            }
        }
    b.确保某个服务在其他服务启动完成后再启动
        import java.util.concurrent.CountDownLatch;

        public class ServiceStartupExample {
            public static void main(String[] args) throws InterruptedException {
                CountDownLatch latch = new CountDownLatch(2);

                new Thread(new Service("Service 1", 1000, latch)).start();
                new Thread(new Service("Service 2", 2000, latch)).start();

                latch.await(); // Wait for both services to start
                System.out.println("All services are up, starting main service.");
            }
        }

        class Service implements Runnable {
            private final String name;
            private final int startupTime;
            private final CountDownLatch latch;

            public Service(String name, int startupTime, CountDownLatch latch) {
                this.name = name;
                this.startupTime = startupTime;
                this.latch = latch;
            }

            @Override
            public void run() {
                try {
                    Thread.sleep(startupTime);
                    System.out.println(name + " is up.");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown(); // Decrement the count
                }
            }
        }
    c.将任务分成多个阶段,每个阶段完成后再执行下一阶段
        import java.util.concurrent.CountDownLatch;

        public class PhasedTaskExample {
            public static void main(String[] args) throws InterruptedException {
                CountDownLatch phase1Latch = new CountDownLatch(3);
                CountDownLatch phase2Latch = new CountDownLatch(3);

                for (int i = 0; i < 3; i++) {
                    new Thread(new PhasedWorker(phase1Latch, phase2Latch)).start();
                }

                phase1Latch.await(); // Wait for phase 1 to complete
                System.out.println("Phase 1 completed.");

                phase2Latch.await(); // Wait for phase 2 to complete
                System.out.println("Phase 2 completed.");
            }
        }

        class PhasedWorker implements Runnable {
            private final CountDownLatch phase1Latch;
            private final CountDownLatch phase2Latch;

            public PhasedWorker(CountDownLatch phase1Latch, CountDownLatch phase2Latch) {
                this.phase1Latch = phase1Latch;
                this.phase2Latch = phase2Latch;
            }

            @Override
            public void run() {
                try {
                    // Phase 1 work
                    Thread.sleep((long) (Math.random() * 1000));
                    System.out.println(Thread.currentThread().getName() + " completed phase 1.");
                    phase1Latch.countDown();

                    // Phase 2 work
                    Thread.sleep((long) (Math.random() * 1000));
                    System.out.println(Thread.currentThread().getName() + " completed phase 2.");
                    phase2Latch.countDown();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }

4.2 [2]CyclicBarrier:循环栅栏,作用跟CountDownLatch类似,但是可以重复使用

01.定义
    CyclicBarrier是一个同步辅助类,允许一组线程在各自完成一组特定操作后互相等待,直到所有线程都到达屏障点
    它可以用于多线程任务的阶段性同步

02.原理
    CyclicBarrier的工作原理是基于一个计数器,该计数器初始化为线程的数量
    每个线程在完成其任务后调用 await() 方法,计数器减一
    当计数器达到零时,所有等待的线程被释放,并且可以选择执行一个可选的屏障操作(由 Runnable 实现)
    CyclicBarrier 可以被重用,因此在所有线程被释放后,计数器会被重置

03.注意事项
    a.BrokenBarrierException
        如果在等待过程中,某个线程被中断或超时,CyclicBarrier 会被打破,所有等待的线程会抛出BrokenBarrierException
    b.重用性
        CyclicBarrier可以被重用,但在所有线程到达屏障点之前,不能重置
    c.屏障操作
        可以指定一个可选的屏障操作,在所有线程到达屏障点后执行

04.常用API
    a.构造函数
        CyclicBarrier(int parties): 创建一个新的 CyclicBarrier,指定需要等待的线程数量
        CyclicBarrier(int parties, Runnable barrierAction): 创建一个新的 CyclicBarrier,指定需要等待的线程数量和一个可选的屏障操作
    b.方法
        int await(): 使当前线程等待,直到所有线程都到达屏障点
        int await(long timeout, TimeUnit unit): 使当前线程等待,直到所有线程都到达屏障点或超时

05.使用步骤
    a.创建CyclicBarrier
        指定需要等待的线程数量和可选的屏障操作
    b.启动线程
        在每个线程中执行任务,并在任务完成后调用await()
    c.等待所有线程
        所有线程调用await()后,继续执行后续操作

06.应用场景及代码示例
    a.简单的线程同步
        import java.util.concurrent.BrokenBarrierException;
        import java.util.concurrent.CyclicBarrier;

        public class CyclicBarrierExample {
            public static void main(String[] args) {
                int numberOfThreads = 3;
                CyclicBarrier barrier = new CyclicBarrier(numberOfThreads, () -> {
                    System.out.println("All threads have reached the barrier. Let's proceed.");
                });

                for (int i = 0; i < numberOfThreads; i++) {
                    new Thread(new Task(barrier)).start();
                }
            }
        }

        class Task implements Runnable {
            private final CyclicBarrier barrier;

            public Task(CyclicBarrier barrier) {
                this.barrier = barrier;
            }

            @Override
            public void run() {
                try {
                    System.out.println(Thread.currentThread().getName() + " is working.");
                    Thread.sleep((long) (Math.random() * 1000));
                    System.out.println(Thread.currentThread().getName() + " has reached the barrier.");
                    barrier.await();
                    System.out.println(Thread.currentThread().getName() + " is proceeding.");
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        }
    b.CyclicBarrier的重用特性
        import java.util.concurrent.BrokenBarrierException;
        import java.util.concurrent.CyclicBarrier;

        public class ReusableCyclicBarrierExample {
            public static void main(String[] args) {
                int numberOfThreads = 3;
                CyclicBarrier barrier = new CyclicBarrier(numberOfThreads, () -> {
                    System.out.println("All threads have reached the barrier. Let's proceed.");
                });

                for (int i = 0; i < numberOfThreads; i++) {
                    new Thread(new ReusableTask(barrier)).start();
                }
            }
        }

        class ReusableTask implements Runnable {
            private final CyclicBarrier barrier;

            public ReusableTask(CyclicBarrier barrier) {
                this.barrier = barrier;
            }

            @Override
            public void run() {
                try {
                    for (int i = 0; i < 3; i++) { // Repeat the process 3 times
                        System.out.println(Thread.currentThread().getName() + " is working on iteration " + i);
                        Thread.sleep((long) (Math.random() * 1000));
                        System.out.println(Thread.currentThread().getName() + " has reached the barrier on iteration " + i);
                        barrier.await();
                        System.out.println(Thread.currentThread().getName() + " is proceeding on iteration " + i);
                    }
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        }

4.3 [3]Semaphor:信号量,限制线程的数量

01.定义
    Semaphore是一个计数信号量,用于限制同时访问某个资源的线程数量
    它通过许可(permits)的概念来管理资源的访问,每个线程在访问资源前必须从信号量中获取许可,并在访问完成后释放许可

02.原理
    基于许可计数器
    信号量初始化时指定许可的数量,表示可以同时访问资源的最大线程数
    线程在访问资源前调用 acquire() 方法获取许可,如果没有可用的许可,线程将被阻塞
    访问完成后,线程调用 release() 方法释放许可,其他等待的线程可以继续获取许可

03.注意事项
    a.许可数量
        Semaphore的许可数量决定了可以同时访问资源的线程数量。确保根据实际需求合理设置许可数量
    b.公平性
        默认情况下,Semaphore是非公平的,即等待时间较短的线程可能会先获得许可
        通过设置公平性,可以确保线程按请求许可的顺序获得许可,但这可能会降低吞吐量
    c.中断处理
        在使用acquire()时,线程可能会被中断,因此需要适当处理InterruptedException

04.常用API
    a.构造函数
        Semaphore(int permits): 创建一个具有指定许可数量的信号量
        Semaphore(int permits, boolean fair): 创建一个具有指定许可数量和公平性设置的信号量
    b.方法
        void acquire(): 获取一个许可,如果没有可用的许可,则阻塞
        void acquire(int permits): 获取指定数量的许可
        void release(): 释放一个许可
        void release(int permits): 释放指定数量的许可
        int availablePermits(): 返回当前可用的许可数量

05.使用步骤
    a.创建Semaphore
        指定许可的数量和可选的公平性设置
    b.获取许可
        在访问资源前调用acquire()
    c.访问资源
        在获取许可后访问共享资源
    d.释放许可
        在访问完成后调用 release()

06.应用场景及代码示例
    a.控制对资源的访问
        import java.util.concurrent.Semaphore;

        public class SemaphoreExample {
            public static void main(String[] args) {
                Semaphore semaphore = new Semaphore(3); // Allow 3 threads to access the resource simultaneously

                for (int i = 0; i < 10; i++) {
                    new Thread(new Worker(semaphore)).start();
                }
            }
        }

        class Worker implements Runnable {
            private final Semaphore semaphore;

            public Worker(Semaphore semaphore) {
                this.semaphore = semaphore;
            }

            @Override
            public void run() {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + " acquired a permit.");
                    // Simulate resource access
                    Thread.sleep((long) (Math.random() * 1000));
                    System.out.println(Thread.currentThread().getName() + " released a permit.");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    semaphore.release();
                }
            }
        }
    b.限制同时执行的任务数量
        import java.util.concurrent.ExecutorService;
        import java.util.concurrent.Executors;
        import java.util.concurrent.Semaphore;

        public class TaskLimitExample {
            public static void main(String[] args) {
                ExecutorService executor = Executors.newFixedThreadPool(10);
                Semaphore semaphore = new Semaphore(3); // Limit to 3 concurrent tasks

                for (int i = 0; i < 10; i++) {
                    executor.submit(() -> {
                        try {
                            semaphore.acquire();
                            System.out.println(Thread.currentThread().getName() + " is executing a task.");
                            // Simulate task execution
                            Thread.sleep((long) (Math.random() * 1000));
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                        } finally {
                            System.out.println(Thread.currentThread().getName() + " finished a task.");
                            semaphore.release();
                        }
                    });
                }

                executor.shutdown();
            }
        }
    c.实现公平性,确保线程按顺序获取许可
        import java.util.concurrent.Semaphore;

        public class FairSemaphoreExample {
            public static void main(String[] args) {
                Semaphore semaphore = new Semaphore(1, true); // Fair semaphore

                for (int i = 0; i < 5; i++) {
                    new Thread(new FairWorker(semaphore)).start();
                }
            }
        }

        class FairWorker implements Runnable {
            private final Semaphore semaphore;

            public FairWorker(Semaphore semaphore) {
                this.semaphore = semaphore;
            }

            @Override
            public void run() {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + " acquired a permit.");
                    // Simulate work
                    Thread.sleep((long) (Math.random() * 1000));
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    System.out.println(Thread.currentThread().getName() + " released a permit.");
                    semaphore.release();
                }
            }
        }

4.4 [4]Exchanger:交换器,两个线程交换数据

01.定义
    Exchanger是一个同步点,两个线程可以在此交换彼此的数据
    每个线程在到达交换点时,必须提供一个对象,并接收另一个线程提供的对象

02.原理
    基于同步交换
    两个线程在调用 exchange() 方法时会互相等待,直到对方也调用 exchange()
    一旦两个线程都到达交换点,它们将交换各自提供的数据,然后继续执行

03.注意事项
    同步点:Exchanger 需要两个线程同时到达同步点才能完成交换,否则线程将一直等待
    超时处理:可以使用带超时的 exchange() 方法来避免无限等待
    线程中断:如果线程在等待交换时被中断,将抛出 InterruptedException

04.常用API
    a.构造函数
        Exchanger(): 创建一个新的 Exchanger 实例
    b.方法
        V exchange(V x): 交换数据,当前线程将提供的数据 x 交换给另一个线程,并接收另一个线程提供的数据
        V exchange(V x, long timeout, TimeUnit unit): 在指定时间内交换数据,如果超时则抛出 TimeoutException

05.使用步骤
    a.创建Exchanger
        实例化一个 Exchanger 对象
    b.启动线程
        在两个线程中调用 exchange() 方法,提供数据并接收对方的数据
    c.处理交换数据
        在交换完成后,处理接收到的数据

06.应用场景及代码示例
    a.两个线程的数据交换
        import java.util.concurrent.Exchanger;

        public class ExchangerExample {
            public static void main(String[] args) {
                Exchanger<String> exchanger = new Exchanger<>();

                Thread thread1 = new Thread(new ExchangeTask(exchanger, "Data from Thread 1"));
                Thread thread2 = new Thread(new ExchangeTask(exchanger, "Data from Thread 2"));

                thread1.start();
                thread2.start();
            }
        }

        class ExchangeTask implements Runnable {
            private final Exchanger<String> exchanger;
            private final String data;

            public ExchangeTask(Exchanger<String> exchanger, String data) {
                this.exchanger = exchanger;
                this.data = data;
            }

            @Override
            public void run() {
                try {
                    System.out.println(Thread.currentThread().getName() + " is exchanging data: " + data);
                    String receivedData = exchanger.exchange(data);
                    System.out.println(Thread.currentThread().getName() + " received data: " + receivedData);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    b.带超时的数据交换
        import java.util.concurrent.Exchanger;
        import java.util.concurrent.TimeUnit;
        import java.util.concurrent.TimeoutException;

        public class TimeoutExchangerExample {
            public static void main(String[] args) {
                Exchanger<String> exchanger = new Exchanger<>();

                Thread thread1 = new Thread(new TimeoutExchangeTask(exchanger, "Data from Thread 1"));
                Thread thread2 = new Thread(new TimeoutExchangeTask(exchanger, "Data from Thread 2"));

                thread1.start();
                thread2.start();
            }
        }

        class TimeoutExchangeTask implements Runnable {
            private final Exchanger<String> exchanger;
            private final String data;

            public TimeoutExchangeTask(Exchanger<String> exchanger, String data) {
                this.exchanger = exchanger;
                this.data = data;
            }

            @Override
            public void run() {
                try {
                    System.out.println(Thread.currentThread().getName() + " is exchanging data: " + data);
                    String receivedData = exchanger.exchange(data, 2, TimeUnit.SECONDS);
                    System.out.println(Thread.currentThread().getName() + " received data: " + receivedData);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } catch (TimeoutException e) {
                    System.out.println(Thread.currentThread().getName() + " timed out while waiting for exchange.");
                }
            }
        }

4.5 [5]Phaser:阶段器,增强的CyclicBarrier

01.定义
    Phaser是一个用于多阶段任务的同步工具,允许一组线程在多个阶段之间进行同步
    每个阶段结束时,线程可以选择继续进入下一个阶段或终止

02.原理
    基于阶段(phase)
    每个 Phaser 实例维护一个阶段计数器,线程在每个阶段结束时调用 arrive() 或 arriveAndAwaitAdvance() 方法
    Phaser 允许动态注册和注销参与者(线程),并在每个阶段结束时自动推进到下一个阶段

03.注意事项
    动态参与者:Phaser 支持动态注册和注销参与者,适合用于参与者数量不固定的场景
    阶段推进:每个阶段结束时,Phaser 自动推进到下一个阶段
    分层结构:Phaser 支持分层结构,可以通过父 Phaser 进行更复杂的同步

04.常用API
    a.构造函数
        Phaser(): 创建一个没有注册参与者的 Phaser
        Phaser(int parties): 创建一个指定初始参与者数量的 Phaser
        Phaser(Phaser parent): 创建一个具有父 Phaser 的 Phaser,用于分层结构
    b.方法
        int register(): 注册一个新的参与者
        int arrive(): 通知 Phaser 当前参与者已到达,不等待其他参与者
        int arriveAndDeregister(): 通知 Phaser 当前参与者已到达并注销
        int arriveAndAwaitAdvance(): 通知 Phaser 当前参与者已到达,并等待其他参与者
        int getPhase(): 获取当前阶段编号
        int getRegisteredParties(): 获取当前注册的参与者数量

05.使用步骤
    a.创建Phaser
        根据需要指定初始参与者数量
    b.注册参与者
        在需要同步的线程中注册为参与者
    c.同步阶段
        在每个阶段结束时调用arriveAndAwaitAdvance()
    d.动态调整
        根据需要动态注册或注销参与者

06.应用场景及代码示例
    a.简单的多阶段同步
        import java.util.concurrent.Phaser;

        public class PhaserExample {
            public static void main(String[] args) {
                Phaser phaser = new Phaser(3); // Initial participants

                for (int i = 0; i < 3; i++) {
                    new Thread(new Task(phaser)).start();
                }
            }
        }

        class Task implements Runnable {
            private final Phaser phaser;

            public Task(Phaser phaser) {
                this.phaser = phaser;
            }

            @Override
            public void run() {
                for (int phase = 0; phase < 3; phase++) {
                    System.out.println(Thread.currentThread().getName() + " is working on phase " + phase);
                    phaser.arriveAndAwaitAdvance(); // Wait for all threads to reach this phase
                }
            }
        }
    b.动态注册和注销功能
        import java.util.concurrent.Phaser;

        public class DynamicPhaserExample {
            public static void main(String[] args) {
                Phaser phaser = new Phaser(1); // Initial participant is the main thread

                for (int i = 0; i < 3; i++) {
                    new Thread(new DynamicTask(phaser)).start();
                }

                phaser.arriveAndDeregister(); // Main thread deregisters
            }
        }

        class DynamicTask implements Runnable {
            private final Phaser phaser;

            public DynamicTask(Phaser phaser) {
                this.phaser = phaser;
                phaser.register(); // Register this thread as a participant
            }

            @Override
            public void run() {
                for (int phase = 0; phase < 3; phase++) {
                    System.out.println(Thread.currentThread().getName() + " is working on phase " + phase);
                    phaser.arriveAndAwaitAdvance(); // Wait for all threads to reach this phase
                }
                phaser.arriveAndDeregister(); // Deregister this thread
            }
        }

4.6 [6]AtomicInteger:计数器,工具类

01.问题描述
    当多个线程同时访问和修改同一个变量时,如果没有适当的同步机制,就会导致数据竞争和不一致的问题
    例如,一个简单的整数计数器在多线程环境下如果不加保护,可能会导致最终的计数值不正确

02.解决方案
    使用AtomicInteger类来实现线程安全的计数器。AtomicInteger提供了一些原子操作方法
    如incrementAndGet()和get(),这些方法可以确保在多线程环境下对计数器的操作是原子的,不会出现竞态条件

03.代码示例
    a.代码
        import java.util.concurrent.atomic.AtomicInteger;

        public class ThreadSafeCounter {
            private final AtomicInteger count = new AtomicInteger(0);

            // Increment the counter in a thread-safe manner
            public void increment() {
                count.incrementAndGet();
            }

            // Get the current value of the counter in a thread-safe manner
            public int getCount() {
                return count.get();
            }

            public static void main(String[] args) throws InterruptedException {
                final ThreadSafeCounter counter = new ThreadSafeCounter();
                int numberOfThreads = 1000;
                Thread[] threads = new Thread[numberOfThreads];

                // Create and start multiple threads to increment the counter
                for (int i = 0; i < numberOfThreads; i++) {
                    threads[i] = new Thread(new Runnable() {
                        @Override
                        public void run() {
                            counter.increment();
                        }
                    });
                    threads[i].start();
                }

                // Wait for all threads to finish
                for (int i = 0; i < numberOfThreads; i++) {
                    threads[i].join();
                }

                // Print the final count value
                System.out.println("Final count: " + counter.getCount());
            }
        }
    b.说明
        a.AtomicInteger
            我们使用了AtomicInteger类来保证计数器的线程安全性
            AtomicInteger提供了一些原子操作方法,如incrementAndGet()和get()
            这些方法可以确保在多线程环境下对计数器的操作是原子的,不会出现竞态条件
        b.多线程测试
            在main方法中,我们创建了1000个线程,每个线程都会调用increment()方法来增加计数器的值
            通过调用join()方法,我们确保主线程等待所有子线程执行完毕
        c.结果验证
            最终,我们打印出计数器的值,应该是1000,因为每个线程都增加了一次计数器

5 锁分类

5.1 [0]分类:8种

00.总结
    乐观锁    假设冲突少,通过版本号或CAS检测冲突                  版本号、CAS、ConcurrentHashMap(CAS实现)
    悲观锁    假设冲突多,先获取锁再访问资源                       synchronized关键字、ReentrantLock可重入锁、ReentrantReadWriteLock读写锁、StampedLock时间戳锁
    可重入锁  允许同一线程多次获取同一把锁                         synchronized关键字、ReentrantLock可重入锁
    公平锁    按请求顺序分配锁,避免线程饥饿                       ReentrantLock可重入锁
    互斥锁    只允许一个线程访问资源,确保线程安全                 synchronized关键字、ReentrantLock可重入锁
    自旋锁    通过循环尝试获取锁,适用于锁持有时间短的场景          CAS、AtomicInteger、自旋次数
    闭锁      控制线程执行顺序,确保操作按顺序进行                 CountDownLatch、CyclicBarrier
    信号量    控制同时访问资源的线程数量,适用于限流和资源池管理    Semaphore

5.2 [1]乐观锁:版本号、CAS、ConcurrentHashMap(CAS实现)

参考【乐观锁】
    假设冲突少,通过版本号或CAS检测冲突

5.3 [2]悲观锁:synchronized关键字、ReentrantLock可重入锁、ReentrantReadWriteLock读写锁、StampedLock时间戳锁

参考【悲观锁】
    假设冲突多,先获取锁再访问资源

5.4 [3]可重入锁:synchronized内置锁、ReentrantLock可重入锁

00.可重入锁
    允许同一线程多次获取同一把锁

01.synchronized内置锁
    a.代码
        public class SynchronizedExample {
            public synchronized void outerMethod() {
                System.out.println(Thread.currentThread().getName() + " entered outerMethod.");
                innerMethod(); // 同一线程可以再次获取锁
            }

            public synchronized void innerMethod() {
                System.out.println(Thread.currentThread().getName() + " entered innerMethod.");
                // 执行代码
            }

            public static void main(String[] args) {
                SynchronizedExample example = new SynchronizedExample();

                // 创建多个线程来测试可重入性
                Thread t1 = new Thread(example::outerMethod, "Thread-1");
                Thread t2 = new Thread(example::outerMethod, "Thread-2");

                t1.start();
                t2.start();
            }
        }
    b.说明
        synchronized:内置锁,允许同一线程多次获取同一把锁

02.ReentrantLock可重入锁
    a.代码
        import java.util.concurrent.locks.ReentrantLock;

        public class ReentrantLockExample {
            private static int sharedData = 0;
            private static ReentrantLock lock = new ReentrantLock();

            public static void main(String[] args) {
                // 创建两个线程并启动
                Thread thread1 = new Thread(() -> {
                    lock.lock(); // 获取锁
                    try {
                        int newValue = sharedData + 1; // 对共享数据进行修改
                        // 模拟耗时操作
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        sharedData = newValue; // 提交修改
                        System.out.println("Thread 1: Shared data updated to " + sharedData);
                        updateSharedData(); // 调用可重入方法
                    } finally {
                        lock.unlock(); // 释放锁
                    }
                });

                Thread thread2 = new Thread(() -> {
                    lock.lock(); // 获取锁
                    try {
                        int newValue = sharedData + 1; // 对共享数据进行修改
                        // 模拟耗时操作
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        sharedData = newValue; // 提交修改
                        System.out.println("Thread 2: Shared data updated to " + sharedData);
                        updateSharedData(); // 调用可重入方法
                    } finally {
                        lock.unlock(); // 释放锁
                    }
                });

                thread1.start();
                thread2.start();
            }

            private static void updateSharedData() {
                lock.lock(); // 获取锁
                try {
                    int newValue = sharedData + 1; // 对共享数据进行修改
                    // 模拟耗时操作
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    sharedData = newValue; // 提交修改
                    System.out.println("Shared data updated inside the reentrant method to " + sharedData);
                } finally {
                    lock.unlock(); // 释放锁
                }
            }
        }
    b.说明
        在这个示例中,我们使用了 ReentrantLock 类来实现可重入锁
        首先,我们创建了一个名为 lock 的 ReentrantLock 对象来保护共享数据
        然后,我们创建了两个线程,在涉及到共享数据的代码块中分别调用 lock() 方法获取锁,并在修改共享数据后调用 unlock() 方法释放锁
        -----------------------------------------------------------------------------------------------------
        值得注意的是,可重入锁允许同一个线程多次获取锁
        在示例中,当线程1获取到锁后,在修改共享数据期间又调用了 updateSharedData() 方法,该方法中也需要获取锁
        由于可重入锁的特性,线程1可以再次获取锁,而不会造成死锁

5.5 [4]公平锁:ReentrantLock可重入锁

00.公平锁
    按请求顺序分配锁,避免线程饥饿

01.ReentrantLock可重入锁
    a.定义
        公平锁是指按照请求锁的顺序来分配锁,确保先请求锁的线程优先获得锁,避免线程饥饿(即某些线程长时间无法获得锁)。
    b.实现
        在创建 ReentrantLock 时,可以通过构造函数参数指定为公平锁:
        ReentrantLock fairLock = new ReentrantLock(true); // 创建公平锁
    c.特点
        避免线程饥饿:公平锁确保线程按照请求的顺序获取锁,避免某些线程长时间等待。
        性能影响:由于公平性,可能导致性能下降,尤其在高竞争的环境中,因为每个线程都必须等待前面的线程释放锁。
    d.代码
        import java.util.concurrent.locks.ReentrantLock;

        public class FairLockExample {
            private static int sharedData = 0;
            private static ReentrantLock lock = new ReentrantLock(true); // 创建公平锁

            public static void main(String[] args) {
                // 创建两个线程并启动
                Thread thread1 = new Thread(() -> {
                    lock.lock(); // 获取锁
                    try {
                        int newValue = sharedData + 1; // 对共享数据进行修改
                        // 模拟耗时操作
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        sharedData = newValue; // 提交修改
                        System.out.println("Thread 1: Shared data updated to " + sharedData);
                    } finally {
                        lock.unlock(); // 释放锁
                    }
                });

                Thread thread2 = new Thread(() -> {
                    lock.lock(); // 获取锁
                    try {
                        int newValue = sharedData + 1; // 对共享数据进行修改
                        // 模拟耗时操作
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        sharedData = newValue; // 提交修改
                        System.out.println("Thread 2: Shared data updated to " + sharedData);
                    } finally {
                        lock.unlock(); // 释放锁
                    }
                });

                thread1.start();
                thread2.start();
            }
        }
        -----------------------------------------------------------------------------------------------------
        在这个示例中,我们使用了 ReentrantLock 类来实现公平锁。
        通过在创建 ReentrantLock 对象时传递参数 true,我们创建了一个公平锁,即等待时间最长的线程会最先获取到锁。
        -----------------------------------------------------------------------------------------------------
        在公平锁中,当多个线程竞争同一个锁时,锁会按照线程等待的顺序分配给它们。
        这可以确保较早等待的线程优先获得锁,避免了饥饿情况的发生,即某些线程一直无法获得锁。
        -----------------------------------------------------------------------------------------------------
        需要注意的是,公平锁可能会牺牲一定的性能,因为它需要维护一个队列来管理等待的线程。
        因此,当性能要求较高且没有特殊需求时,可以使用非公平锁。
        -----------------------------------------------------------------------------------------------------
        在实际开发中,公平锁的选择应根据具体的业务需求和性能要求综合考虑。

5.6 [5]互斥锁:synchronized关键字、ReentrantLock可重入锁

00.互斥锁
    只允许一个线程访问资源,确保线程安全

01.ReentrantLock可重入锁
    a.定义
        ReentrantLock 是 Java 并发包中的一个类,提供了可重入锁的实现。它可以配置为公平锁或非公平锁。
        公平锁确保线程按照请求的顺序获取锁,避免线程饥饿。
    b.实现
        创建公平锁的方式:
        ReentrantLock fairLock = new ReentrantLock(true); // 创建公平锁
    c.特点
        避免线程饥饿:公平锁确保先请求锁的线程优先获得锁。
        可重入性:同一线程可以多次获取同一把锁,而不会发生死锁。
        性能影响:由于公平性,可能导致性能下降,尤其在高竞争的环境中,因为每个线程都必须等待前面的线程释放锁。
    d.代码
        import java.util.concurrent.locks.Lock;
        import java.util.concurrent.locks.ReentrantLock;

        public class MutexLockExample {
            private static int sharedData = 0;
            private static Lock lock = new ReentrantLock();

            public static void main(String[] args) {
                // 创建两个线程并启动
                Thread thread1 = new Thread(() -> {
                    lock.lock(); // 获取锁
                    try {
                        int newValue = sharedData + 1; // 对共享数据进行修改
                        // 模拟耗时操作
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        sharedData = newValue; // 提交修改
                        System.out.println("Thread 1: Shared data updated to " + sharedData);
                    } finally {
                        lock.unlock(); // 释放锁
                    }
                });

                Thread thread2 = new Thread(() -> {
                    lock.lock(); // 获取锁
                    try {
                        int newValue = sharedData + 1; // 对共享数据进行修改
                        // 模拟耗时操作
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        sharedData = newValue; // 提交修改
                        System.out.println("Thread 2: Shared data updated to " + sharedData);
                    } finally {
                        lock.unlock(); // 释放锁
                    }
                });

                thread1.start();
                thread2.start();
            }
        }
        -----------------------------------------------------------------------------------------------------
        在这个示例中,我们使用了 ReentrantLock 类来实现互斥锁。
        通过创建一个 ReentrantLock 对象,我们获得了一个可重入锁。
        这意味着同一个线程可以多次获取同一个锁而不会发生死锁。
        -----------------------------------------------------------------------------------------------------
        在示例中,当一个线程获取到锁后,其他线程将被阻塞直到锁被释放。
        这样确保了同时只有一个线程能够修改共享数据,从而避免了数据竞争和并发问题。
        -----------------------------------------------------------------------------------------------------
        需要注意的是,在使用互斥锁时,务必在合适的地方调用 unlock() 方法来释放锁,以避免死锁和资源泄漏。
        互斥锁是一种常见且有效的保护共享资源的机制,在并发编程中被广泛使用。使用互斥锁可以确保共享数据的一致性和线程安全。

5.7 [6]自旋锁:CAS、AtomicInteger、自旋次数

00.自旋锁
    a.定义
        只允许一个线程访问资源,确保线程安全
        自旋锁是一种忙等待的锁机制,在线程尝试获得锁时不会立即阻塞,而是循环检测锁的状态,直到成功获取锁或达到最大自旋次数。
    b.基于CAS的自旋锁
        使用AtomicBoolean和CAS操作实现自旋锁,通过原子操作不断尝试获取锁。
    c.基于AtomicInteger的自旋锁
        使用AtomicInteger和CAS操作实现自旋锁,通过原子操作不断尝试将锁状态从0更新为1。
    d.基于自旋次数的自旋锁
        在自旋锁的基础上增加自旋次数限制,避免长时间自旋导致CPU资源浪费。

01.基于CAS的自旋锁
    a.定义
        使用CAS(Compare-And-Swap)操作实现自旋锁,通过原子操作不断尝试获取锁,直到成功
    b.代码
        import java.util.concurrent.atomic.AtomicBoolean;

        public class CASSpinLock {
            private final AtomicBoolean lock = new AtomicBoolean(false);

            public void lock() {
                while (!lock.compareAndSet(false, true)) {
                    // 自旋等待
                }
            }

            public void unlock() {
                lock.set(false);
            }

            public static void main(String[] args) {
                CASSpinLock spinLock = new CASSpinLock();

                Runnable task = () -> {
                    spinLock.lock();
                    try {
                        System.out.println(Thread.currentThread().getName() + " acquired the lock");
                    } finally {
                        spinLock.unlock();
                    }
                };

                Thread t1 = new Thread(task);
                Thread t2 = new Thread(task);

                t1.start();
                t2.start();
            }
        }

02.基于AtomicInteger的自旋锁
    a.定义
        使用AtomicInteger实现自旋锁,通过CAS操作不断尝试将锁状态从0更新为1,直到成功
    b.代码
        import java.util.concurrent.atomic.AtomicInteger;

        public class AtomicIntegerSpinLock {
            private final AtomicInteger lock = new AtomicInteger(0);

            public void lock() {
                while (!lock.compareAndSet(0, 1)) {
                    // 自旋等待
                }
            }

            public void unlock() {
                lock.set(0);
            }

            public static void main(String[] args) {
                AtomicIntegerSpinLock spinLock = new AtomicIntegerSpinLock();

                Runnable task = () -> {
                    spinLock.lock();
                    try {
                        System.out.println(Thread.currentThread().getName() + " acquired the lock");
                    } finally {
                        spinLock.unlock();
                    }
                };

                Thread t1 = new Thread(task);
                Thread t2 = new Thread(task);

                t1.start();
                t2.start();
            }
        }

03.基于自旋次数的自旋锁
    a.定义
        在自旋锁的基础上,增加自旋次数限制,避免长时间自旋导致CPU资源浪费。
        如果超过自旋次数限制,线程可以选择休眠一段时间或放弃自旋。
    b.代码
        import java.util.concurrent.atomic.AtomicBoolean;

        public class SpinLockWithLimit {
            private final AtomicBoolean lock = new AtomicBoolean(false);
            private final int spinLimit;

            public SpinLockWithLimit(int spinLimit) {
                this.spinLimit = spinLimit;
            }

            public void lock() {
                int spins = 0;
                while (!lock.compareAndSet(false, true)) {
                    if (++spins >= spinLimit) {
                        try {
                            Thread.sleep(1); // 休眠一段时间
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                        }
                        spins = 0;
                    }
                }
            }

            public void unlock() {
                lock.set(false);
            }

            public static void main(String[] args) {
                SpinLockWithLimit spinLock = new SpinLockWithLimit(100);

                Runnable task = () -> {
                    spinLock.lock();
                    try {
                        System.out.println(Thread.currentThread().getName() + " acquired the lock");
                    } finally {
                        spinLock.unlock();
                    }
                };

                Thread t1 = new Thread(task);
                Thread t2 = new Thread(task);

                t1.start();
                t2.start();
            }
        }

5.8 [7]闭锁:CountDownLatch、CyclicBarrier

00.闭锁
    a.定义
        控制线程执行顺序,确保操作按顺序进行
    b.CountDownLatch
        用于等待一组操作完成,计数器减到零后唤醒所有等待的线程。
        适用于一次性等待场景。
    c.CyclicBarrier
        用于让一组线程在某个点上相互等待,所有线程到达后继续执行。
        适用于循环等待场景,计数器可以重用。

01.CountDownLatch
    a.定义
        CountDownLatch 是一个同步辅助类,允许一个或多个线程等待直到一组操作完成。
        它维护一个计数器,初始值由构造函数指定,表示需要等待的事件数量。
    b.工作原理
        线程在调用 await() 方法时会被阻塞,直到计数器的值减为零。
        其他线程通过调用 countDown() 方法来减少计数器的值。
        一旦计数器减为零,所有等待的线程将被唤醒。
    c.使用场景
        适用于需要等待多个线程完成某些操作后再继续执行的场景,例如在并行任务完成后进行汇总。
    d.代码示例
        import java.util.concurrent.CountDownLatch;

        public class CountDownLatchExample {
            public static void main(String[] args) throws InterruptedException {
                CountDownLatch latch = new CountDownLatch(3);

                Runnable task = () -> {
                    try {
                        Thread.sleep(1000); // 模拟任务执行
                        System.out.println(Thread.currentThread().getName() + " finished.");
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    } finally {
                        latch.countDown(); // 任务完成,计数器减一
                    }
                };

                for (int i = 0; i < 3; i++) {
                    new Thread(task).start();
                }

                latch.await(); // 等待所有任务完成
                System.out.println("All tasks finished.");
            }
        }

02.CyclicBarrier
    a.定义
        CyclicBarrier 是一个同步辅助类,允许一组线程在某个点上相互等待,直到所有线程都到达该点。
        它的计数器可以重用,因此称为“循环屏障”。
    b.工作原理
        线程在调用 await() 方法时会被阻塞,直到到达屏障的线程数量达到指定的值。
        一旦所有线程都到达屏障,计数器会重置,允许线程继续执行。
    c.使用场景
        适用于需要多个线程在某个阶段同步执行的场景,例如在并行计算中,多个线程需要在某个点汇合后再继续执行。
    d.代码示例
        import java.util.concurrent.CyclicBarrier;

        public class CyclicBarrierExample {
            public static void main(String[] args) throws InterruptedException {
                CyclicBarrier barrier = new CyclicBarrier(3, () -> {
                    System.out.println("All threads reached the barrier, proceeding...");
                });

                Runnable task = () -> {
                    try {
                        Thread.sleep(1000); // 模拟任务执行
                        System.out.println(Thread.currentThread().getName() + " reached the barrier.");
                        barrier.await(); // 等待其他线程
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                };

                for (int i = 0; i < 3; i++) {
                    new Thread(task).start();
                }
            }
        }

5.9 [8]信号量:Semaphore类

00.信号量
    控制同时访问资源的线程数量,适用于限流和资源池管理

01.Semaphore类
    a.定义
        Semaphore是一个计数信号量,维护一个许可(permit)的数量,允许多个线程同时访问共享资源
    b.特点
        计数器:信号量内部维护一个计数器,表示可用的许可数量
        获取和释放许可:线程可以通过获取许可来访问资源,使用完毕后释放许可
        阻塞和非阻塞:如果没有可用的许可,线程可以选择阻塞等待或立即返回
    c.常用方法
        a.构造方法
            Semaphore(int permits): 创建一个信号量,初始许可数量为permits
        b.获取许可
            void acquire(): 获取一个许可,如果没有可用的许可,则阻塞等待
            void acquire(int permits): 获取指定数量的许可
            boolean tryAcquire(): 尝试获取一个许可,如果成功则返回true,否则返回false
            boolean tryAcquire(int permits): 尝试获取指定数量的许可
        c.释放许可
            void release(): 释放一个许可
            void release(int permits): 释放指定数量的许可
        d.可用许可数量
            int availablePermits(): 返回当前可用的许可数量
    d.代码
        import java.util.concurrent.Semaphore;

        public class SemaphoreExample {
            public static void main(String[] args) {
                int workerCount = 5; // 工作线程数目
                Semaphore semaphore = new Semaphore(2); // 信号量,初始许可证数量为2

                // 创建工作线程并启动
                for (int i = 0; i < workerCount; i++) {
                    Thread thread = new Thread(() -> {
                        try {
                            semaphore.acquire(); // 获取许可证,如果没有可用的许可证,则阻塞等待
                            System.out.println("Worker thread start");
                            // 模拟工作
                            Thread.sleep(1000);
                            System.out.println("Worker thread finish");
                            semaphore.release(); // 释放许可证
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    });
                    thread.start();
                }
            }
        }
        -----------------------------------------------------------------------------------------------------
        在这个示例中,我们使用了 Semaphore 类来实现信号量。信号量是一种同步工具,它可以控制对某个资源的访问数量。
        在示例中,创建了一个初始许可证数量为2的信号量(semaphore)。然后,创建了多个工作线程并启动。
        每个工作线程在开始工作之前调用 acquire() 方法来获取许可证。如果当前有可用的许可证,则线程获取到许可证并继续执行工作。
        如果当前没有可用的许可证,则线程会阻塞等待,直到有其他线程释放许可证。
        工作线程完成工作后调用 release() 方法来释放许可证,使得其他等待的线程可以获取许可证继续执行工作。
        通过信号量,我们可以控制同时访问某个资源的线程数量,实现对并发访问的控制和限制。

6 锁场景

6.1 [0]汇总

01.读多写少
    ReentrantReadWriteLock + ConcurrentHashMap              使用读写锁来优化读操作,使用 ConcurrentHashMap 来支持高并发读写
    AtomicReference        + CopyOnWriteArrayList           使用 CopyOnWriteArrayList 来优化读操作,使用 AtomicReference 来确保写操作的原子性
    StampedLock(乐观读)   + ConcurrentHashMap              使用 StampedLock 的乐观读锁来优化读操作,使用 ConcurrentHashMap 来支持高并发读写

02.读少写多
    ReentrantLock            + BlockingQueue                使用 ReentrantLock 来控制写操作,使用 BlockingQueue 来支持生产者-消费者模式
    AtomicInteger/AtomicLong + ConcurrentLinkedQueue        使用原子类来优化计数器的更新,使用 ConcurrentLinkedQueue 来支持高并发写操作
    Semaphore                + ArrayBlockingQueue           使用信号量来限制并发写操作,使用 ArrayBlockingQueue 来支持阻塞操作

03.读多写多
    StampedLock(乐观读 + 悲观写) + ConcurrentHashMap       使用 StampedLock 的乐观读锁和悲观写锁来平衡读写操作,使用 ConcurrentHashMap 来支持高并发读写
    ReentrantReadWriteLock        + LongAdder/DoubleAdder   使用读写锁来优化读写操作,使用累加器来优化计数器的更新
    ForkJoinPool                  + ConcurrentHashMap       使用 ForkJoinPool 来支持并行任务执行,使用 ConcurrentHashMap 来支持高并发读写

04.其他组合
    ThreadLocal + AtomicReference                           使用 ThreadLocal 来为每个线程提供独立的变量副本,使用 AtomicReference 来确保写操作的原子性
    Phaser + ConcurrentSkipListMap                          使用 Phaser 来协调多阶段任务,使用 ConcurrentSkipListMap 来支持有序的高并发读写
    Exchanger + ConcurrentLinkedDeque                       使用 Exchanger 来交换数据,使用 ConcurrentLinkedDeque 来支持双端队列的高并发操作

6.2 [0]优化:锁持有时间、锁粒度

00.汇总
    1.减少锁的持有时间
    2.减少锁的粒度

01.减少锁的持有时间
    a.定义
        减少锁的持有时间是指尽量缩短线程持有锁的时间,以减少锁竞争和等待时间。
    b.方法
        优化代码:将需要加锁的代码块尽量缩小,只在必要的地方加锁,避免在锁内执行耗时操作。
        使用局部变量:在锁外部进行计算,将结果赋值给局部变量,然后在锁内使用这些局部变量,减少锁内的操作时间。
        减少锁的嵌套:避免在一个锁内再获取另一个锁,减少死锁的风险和锁竞争。
    c.示例
        public class LockOptimization {
            private final Object lock = new Object();
            private int sharedResource;

            public void optimizedMethod() {
                int localVar = computeOutsideLock();
                synchronized (lock) {
                    sharedResource = localVar;
                }
            }

            private int computeOutsideLock() {
                // 计算逻辑在锁外部执行
                return 42;
            }
        }

02.减少锁的粒度
    a.定义
        减少锁的粒度是指将大锁拆分为多个小锁,以减少锁的竞争,提高并发度。
    b.方法
        细化锁:将一个大锁拆分为多个小锁,每个小锁只保护特定的资源或数据。
        分段锁(Segmented Locking):将数据分段,每段数据使用不同的锁,常用于并发集合类(如ConcurrentHashMap)。
        读写锁(Read-Write Lock):使用读写锁(如ReentrantReadWriteLock),允许多个读线程并发访问,但写线程独占锁。
    c.示例
        public class FineGrainedLocking {
            private final Object lock1 = new Object();
            private final Object lock2 = new Object();
            private int resource1;
            private int resource2;

            public void method1() {
                synchronized (lock1) {
                    resource1++;
                }
            }

            public void method2() {
                synchronized (lock2) {
                    resource2++;
                }
            }
        }

6.3 [1]读多写少:3种

00.汇总
    ReentrantReadWriteLock + ConcurrentHashMap              使用读写锁来优化读操作,使用 ConcurrentHashMap 来支持高并发读写
    AtomicReference        + CopyOnWriteArrayList           使用 CopyOnWriteArrayList 来优化读操作,使用 AtomicReference 来确保写操作的原子性
    StampedLock(乐观读)   + ConcurrentHashMap              使用 StampedLock 的乐观读锁来优化读操作,使用 ConcurrentHashMap 来支持高并发读写

01.ReentrantReadWriteLock + ConcurrentHashMap
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.locks.ReentrantReadWriteLock;

    public class ReadMostlyExample1 {
        private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
        private final ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();

        public void readOperation() {
            rwLock.readLock().lock();
            try {
                System.out.println("Reading from map: " + map);
            } finally {
                rwLock.readLock().unlock();
            }
        }

        public void writeOperation(String key, String value) {
            rwLock.writeLock().lock();
            try {
                map.put(key, value);
            } finally {
                rwLock.writeLock().unlock();
            }
        }

        public static void main(String[] args) {
            ReadMostlyExample1 example = new ReadMostlyExample1();
            example.writeOperation("key", "value");
            example.readOperation();
        }
    }

02.AtomicReference + CopyOnWriteArrayList
    import java.util.concurrent.CopyOnWriteArrayList;
    import java.util.concurrent.atomic.AtomicReference;

    public class ReadMostlyExample2 {
        private final CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        private final AtomicReference<String> ref = new AtomicReference<>("Initial");

        public void readOperation() {
            System.out.println("Reading from list: " + list);
            System.out.println("Reading from ref: " + ref.get());
        }

        public void writeOperation(String value) {
            list.add(value);
            ref.set(value);
        }

        public static void main(String[] args) {
            ReadMostlyExample2 example = new ReadMostlyExample2();
            example.writeOperation("New Element");
            example.readOperation();
        }
    }

03.StampedLock(乐观读) + ConcurrentHashMap
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.locks.StampedLock;

    public class ReadMostlyExample3 {
        private final StampedLock lock = new StampedLock();
        private final ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();

        public void readOperation() {
            long stamp = lock.tryOptimisticRead();
            try {
                System.out.println("Reading from map: " + map);
            } finally {
                if (!lock.validate(stamp)) {
                    stamp = lock.readLock();
                    try {
                        System.out.println("Re-reading from map: " + map);
                    } finally {
                        lock.unlockRead(stamp);
                    }
                }
            }
        }

        public void writeOperation(String key, String value) {
            long stamp = lock.writeLock();
            try {
                map.put(key, value);
            } finally {
                lock.unlockWrite(stamp);
            }
        }

        public static void main(String[] args) {
            ReadMostlyExample3 example = new ReadMostlyExample3();
            example.writeOperation("key", "value");
            example.readOperation();
        }
    }

6.4 [2]读少写多:3种

00.汇总
    ReentrantLock            + BlockingQueue                使用 ReentrantLock 来控制写操作,使用 BlockingQueue 来支持生产者-消费者模式
    AtomicInteger/AtomicLong + ConcurrentLinkedQueue        使用原子类来优化计数器的更新,使用 ConcurrentLinkedQueue 来支持高并发写操作
    Semaphore                + ArrayBlockingQueue           使用信号量来限制并发写操作,使用 ArrayBlockingQueue 来支持阻塞操作

01.ReentrantLock + BlockingQueue
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.locks.ReentrantLock;

    public class WriteMostlyExample1 {
        private final ReentrantLock lock = new ReentrantLock();
        private final BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);

        public void writeOperation(String value) {
            lock.lock();
            try {
                queue.put(value);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                lock.unlock();
            }
        }

        public void readOperation() {
            lock.lock();
            try {
                System.out.println("Queue: " + queue);
            } finally {
                lock.unlock();
            }
        }

        public static void main(String[] args) {
            WriteMostlyExample1 example = new WriteMostlyExample1();
            example.writeOperation("New Element");
            example.readOperation();
        }
    }

02.AtomicInteger/AtomicLong + ConcurrentLinkedQueue
    import java.util.concurrent.ConcurrentLinkedQueue;
    import java.util.concurrent.atomic.AtomicInteger;

    public class WriteMostlyExample2 {
        private final AtomicInteger counter = new AtomicInteger(0);
        private final ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();

        public void writeOperation(String value) {
            queue.offer(value);
            counter.incrementAndGet();
        }

        public void readOperation() {
            System.out.println("Counter: " + counter.get());
            System.out.println("Queue: " + queue);
        }

        public static void main(String[] args) {
            WriteMostlyExample2 example = new WriteMostlyExample2();
            example.writeOperation("New Element");
            example.readOperation();
        }
    }

03.Semaphore + ArrayBlockingQueue
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.Semaphore;

    public class WriteMostlyExample3 {
        private final Semaphore semaphore = new Semaphore(1);
        private final BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);

        public void writeOperation(String value) {
            try {
                semaphore.acquire();
                queue.put(value);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                semaphore.release();
            }
        }

        public void readOperation() {
            System.out.println("Queue: " + queue);
        }

        public static void main(String[] args) {
            WriteMostlyExample3 example = new WriteMostlyExample3();
            example.writeOperation("New Element");
            example.readOperation();
        }
    }

6.5 [3]读多写多:3种

00.汇总
    StampedLock(乐观读 + 悲观写) + ConcurrentHashMap       使用 StampedLock 的乐观读锁和悲观写锁来平衡读写操作,使用 ConcurrentHashMap 来支持高并发读写
    ReentrantReadWriteLock        + LongAdder/DoubleAdder   使用读写锁来优化读写操作,使用累加器来优化计数器的更新
    ForkJoinPool                  + ConcurrentHashMap       使用 ForkJoinPool 来支持并行任务执行,使用 ConcurrentHashMap 来支持高并发读写

01.StampedLock(乐观读 + 悲观写) + ConcurrentHashMap
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.locks.StampedLock;

    public class ReadWriteExample1 {
        private final StampedLock lock = new StampedLock();
        private final ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();

        public void readOperation() {
            long stamp = lock.tryOptimisticRead();
            try {
                System.out.println("Reading from map: " + map);
            } finally {
                if (!lock.validate(stamp)) {
                    stamp = lock.readLock();
                    try {
                        System.out.println("Re-reading from map: " + map);
                    } finally {
                        lock.unlockRead(stamp);
                    }
                }
            }
        }

        public void writeOperation(String key, String value) {
            long stamp = lock.writeLock();
            try {
                map.put(key, value);
            } finally {
                lock.unlockWrite(stamp);
            }
        }

        public static void main(String[] args) {
            ReadWriteExample1 example = new ReadWriteExample1();
            example.writeOperation("key", "value");
            example.readOperation();
        }
    }

02.ReentrantReadWriteLock + LongAdder/DoubleAdder
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    import java.util.concurrent.atomic.LongAdder;

    public class ReadWriteExample2 {
        private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
        private final LongAdder counter = new LongAdder();

        public void readOperation() {
            rwLock.readLock().lock();
            try {
                System.out.println("Counter: " + counter.sum());
            } finally {
                rwLock.readLock().unlock();
            }
        }

        public void writeOperation() {
            rwLock.writeLock().lock();
            try {
                counter.increment();
            } finally {
                rwLock.writeLock().unlock();
            }
        }

        public static void main(String[] args) {
            ReadWriteExample2 example = new ReadWriteExample2();
            example.writeOperation();
            example.readOperation();
        }
    }

03.ForkJoinPool + ConcurrentHashMap
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.ForkJoinPool;
    import java.util.concurrent.RecursiveTask;

    public class ReadWriteExample3 {
        private final ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
        private final ForkJoinPool forkJoinPool = new ForkJoinPool();

        public void readOperation() {
            forkJoinPool.submit(() -> {
                System.out.println("Reading from map: " + map);
            }).join();
        }

        public void writeOperation(String key, String value) {
            forkJoinPool.submit(() -> {
                map.put(key, value);
            }).join();
        }

        public static void main(String[] args) {
            ReadWriteExample3 example = new ReadWriteExample3();
            example.writeOperation("key", "value");
            example.readOperation();
        }
    }

6.6 [4]其他组合:3种

00.汇总
    ThreadLocal + AtomicReference                           使用 ThreadLocal 来为每个线程提供独立的变量副本,使用 AtomicReference 来确保写操作的原子性
    Phaser + ConcurrentSkipListMap                          使用 Phaser 来协调多阶段任务,使用 ConcurrentSkipListMap 来支持有序的高并发读写
    Exchanger + ConcurrentLinkedDeque                       使用 Exchanger 来交换数据,使用 ConcurrentLinkedDeque 来支持双端队列的高并发操作

01.ThreadLocal + AtomicReference
    import java.util.concurrent.atomic.AtomicReference;

    public class OtherCombination1 {
        private final ThreadLocal<AtomicReference<String>> threadLocalRef = ThreadLocal.withInitial(() -> new AtomicReference<>("Initial"));

        public void readOperation() {
            System.out.println("ThreadLocal value: " + threadLocalRef.get().get());
        }

        public void writeOperation(String value) {
            threadLocalRef.get().set(value);
        }

        public static void main(String[] args) {
            OtherCombination1 example = new OtherCombination1();
            example.writeOperation("New Value");
            example.readOperation();
        }
    }

02.Phaser + ConcurrentSkipListMap
    import java.util.concurrent.ConcurrentSkipListMap;
    import java.util.concurrent.Phaser;

    public class OtherCombination2 {
        private final Phaser phaser = new Phaser(1);
        private final ConcurrentSkipListMap<String, String> map = new ConcurrentSkipListMap<>();

        public void readOperation() {
            phaser.arriveAndAwaitAdvance();
            System.out.println("Reading from map: " + map);
        }

        public void writeOperation(String key, String value) {
            phaser.arriveAndAwaitAdvance();
            map.put(key, value);
        }

        public static void main(String[] args) {
            OtherCombination2 example = new OtherCombination2();
            example.writeOperation("key", "value");
            example.readOperation();
        }
    }

03.Exchanger + ConcurrentLinkedDeque
    import java.util.concurrent.ConcurrentLinkedDeque;
    import java.util.concurrent.Exchanger;

    public class OtherCombination3 {
        private final Exchanger<String> exchanger = new Exchanger<>();
        private final ConcurrentLinkedDeque<String> deque = new ConcurrentLinkedDeque<>();

        public void readOperation() {
            try {
                String value = exchanger.exchange(null);
                System.out.println("Exchanged value: " + value);
                System.out.println("Deque: " + deque);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }

        public void writeOperation(String value) {
            try {
                exchanger.exchange(value);
                deque.add(value);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }

        public static void main(String[] args) {
            OtherCombination3 example = new OtherCombination3();
            new Thread(() -> example.writeOperation("New Value")).start();
            new Thread(example::readOperation).start();
        }
    }