java并发-模拟秒杀

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_22271479/article/details/85096547

模拟秒杀

思路

在这里插入图片描述

  • 对每个秒杀请求入队操作
  • 当库存为N时,队列的长度超过N时,可以考虑拒绝后续请求,直接响应客户端秒杀结束
  • 为了减轻库存处理的压力,验证并发量,这里通过信号量来控制线程安全。

编码

  • 通过Semaphore来控制并发量
  • 通过CAS来控制更新库存,保证线程安全
/***
 * 模拟秒杀
 */
public class Knock {

    /**
     * CAS操作的类
      */
    private static final Unsafe unsafe;
    /**
     * total的偏移量
     */
    private static final long totalOffset;

    /**
     * 单实例
     */
    private static volatile Knock knock;

    /**
     * 总数量
     */
    private volatile int total;

    /**
     * 信号量
     */
    private Semaphore semaphore;

    /**
     * 初始化UnSafe
     * 只能通过反射实例化
     * 在catch出只能抛出Error,unsafe只能初始化一次
     */
    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);
            totalOffset = unsafe.objectFieldOffset(Knock.class.getDeclaredField("total"));
        } catch (Exception e) {
            e.printStackTrace();
            throw new Error(e);
        }

    }

    /**
     * 初始化公平的信号量
     *
     * @param total          总数
     * @param threadsPerTime 线程所需要的信号,控制并发量
     */
    private Knock(int total, int threadsPerTime) {
        this.total = total;
        semaphore = new Semaphore(threadsPerTime, true);
    }

    /**
     * 单例 lazy
     *
     * @param total
     * @param threadsPerTime
     * @return
     */
    public static Knock getInstance(int total, int threadsPerTime) {
        //one check
        if (null != knock) {
            return knock;
        }
        //double check
        synchronized (Knock.class) {
            if (null == knock) {
                //knock需要加上volatile关键字,1.禁止重排序 2.线程间可见
                knock = new Knock(total, threadsPerTime);
            }
        }
        return knock;
    }

    public int getTotal() {
        return total;
    }

    /**
     * CAS 减法
     * 死循环退出的条件
     * 1. 总数大于0的情况下,去做一次CAS操作,操作成功,则返回,失败则循环
     * 2. 如果total==0的情况下,直接返回false,终止抢购
     * @return
     */
    public boolean casTotal(int except) {
        for (; ; ) {
            if (total > 0) {
                int update = total - 1;
                if (unsafe.compareAndSwapInt(this, totalOffset, except, update)) {
                    return true;
                }
            } else {
                return false;
            }

        }
    }

    /**
     * 抢购
     *
     * @param need
     */
    public void doKnock(int need) {
        //当队列的长度时商品总量的两倍就返回,抢购失败
        //当total==0的时候,抢购失败
        if (semaphore.getQueueLength() > (total << 1) || total == 0) {
            System.out.println(Thread.currentThread().getId() + ":已售罄!");
            return;
        }
        //抢购
        try {
            //获取资源
            semaphore.acquire(need);
            //这里要check total的值,不符合就直接返回
            if (total == 0) {
                System.out.println(Thread.currentThread().getId() + "已售罄!");
                return;
            }
            //这里必须通过局部变量接收,因为信号量模式,同一时间有多个线程在同时执行,是线程不安全的
            int expect = total;
            //CAS修改当前库存
            if (casTotal(expect)) {
                //current是当前线程消费过后的库存
                int current = expect - 1;
                System.out.println(Thread.currentThread().getId() + "当前剩余:" + current);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //释放资源
            semaphore.release(need);
        }
    }
}

测试,通过CPU核心数,去控制并发的线程,提高QPS

public class KTest {

    static class T extends Thread {
        private Knock knock;
        private int need;

        public T(Knock knock, int need) {
            this.knock = knock;
            this.need = need;
        }

        @Override
        public void run() {
            knock.doKnock(need);
        }
    }

    public static void main(String[] args) {
        //获取可用CPU核心数
        int availableProcessors = Runtime.getRuntime().availableProcessors();
        long s = System.currentTimeMillis();
        Knock knock = Knock.getInstance(1100, availableProcessors*2);
        for (int i = 0; i < 2000; i++) {
            T t = new T(knock, 1);
            try {
                t.start();
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        long e = System.currentTimeMillis();
        System.out.println(knock.getTotal() + "======================"+(e-s));
    }
}

GitHub主页

猜你喜欢

转载自blog.csdn.net/qq_22271479/article/details/85096547
今日推荐