zoukankan      html  css  js  c++  java
  • jedis解决高并发的一些学习

    1、高并发带来的问题就是  {公共资源 } 的读写不准确

    2、解决高并发的几种场景:

      场景一)  同一个JVM进程(jee中就是同一个tomcat)中,公共资源在同一块内存中,使用synchronized关键字给代码块或是方法加锁,使得同一个代码块不会被同时调用;成员变量的数据类型尽量使用JUC中的atomic*等原子类,其中的方法都是原子操作,但是性能会有所降低;对于非原子类成员变量修饰符可以使用volatile(强制使用主存变量)。

        说明:JUC在此场景中的使用非常广泛,主要是CAS操作,而且多线程的公共资源都是在主存中进行读取,不会在寄存器中做修改;但是,会有一个ABA问题(比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然         后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功)

      场景二)  非同一个JVM进程(集群或分布式),公共资源在第三方缓存中(如redis),JUC的CAS操作会失效,这个时候想实现公共资源的正确调用只能依赖第三方的同步锁机制(如redis的WATCH监控和事务MULTI-EXEC的配合使用)

    3、实际应用:

      (1)jedis解决抢购的问题:

    package com.thinkgem.jeesite;
    
    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.JedisPool;
    import redis.clients.jedis.JedisPoolConfig;
    import redis.clients.jedis.Transaction;
    
    import java.util.List;
    import java.util.UUID;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * Created by Administrator on 2017/9/29.
     */
    public class JedisMainTest {
        static final JedisPool pool = new JedisPool(new JedisPoolConfig() {{
            setMaxIdle(1000);
            setMaxTotal(1000);
            setTestOnBorrow(true);
        }}, "127.0.0.1", 6379);
    
        static final  String watchkeys = "watchKeys";// 监视keys
        static final String goodsStore = "goodsStore";//库存的key
        static final int stores = 10;//库存量
    
        public static void main(String[] args) throws InterruptedException {
    
    
    
    
            ExecutorService executor = Executors.newFixedThreadPool(1000);
            ExecutorService executor1 = Executors.newFixedThreadPool(1000);
            Jedis jedis = pool.getResource();
            jedis.set(watchkeys, "0");// 重置watchkeys为0
            jedis.set(goodsStore,""+stores);
            jedis.del("setsucc", "setfail");// 清空抢成功的,与没有成功的
            jedis.close();
    
            for (int i = 0; i < 10000; i++) {// 测试一万人同时访问
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        for (int c = 0; c < 10; c++) {
    
                            executor1.execute(new MyRunnable());
                        }
                    }
                });
    //            Thread.sleep(50);
            }
            executor.shutdown();
        }
    
    
        static class MyRunnable implements Runnable {
    /*TODO:未解决问题,一开始有两个以上的线程同时执行,会出现库存有剩余,但是同时执行的程序抢不到库存*/
            Jedis jedis = pool.getResource();
    
            public MyRunnable() {
            }
    
            @Override
            public void run() {
                try {
    
                    jedis.watch(watchkeys);// 开始监视锁
    
                    String val = jedis.get(goodsStore);
                    int valint = Integer.valueOf(val);
                    String userifo = UUID.randomUUID().toString();
                    if (valint >0) {
                        Transaction tx = jedis.multi();// 开启事务
    
                        tx.decr(goodsStore);//库存量 -1
                        tx.incr(watchkeys);//修改监控变量
    
                        List<Object> list = tx.exec();// 提交事务,如果此时watchkeys被改动了,则返回null
    
                        //说明通过验证了,即库存还有剩余
                        if (list != null) {
                            System.out.println("用户:" + userifo + "抢购成功,当前抢购成功人数:"
                                    + (stores-valint+1));
                        /* 抢购成功业务逻辑 */
                            jedis.sadd("setsucc", userifo);
                            Thread.sleep(900);//模拟业务执行时间
                        } else {
                            System.out.println("用户:" + userifo + "抢购失败,其他线程干扰");
                        /* 抢购失败业务逻辑 */
                            jedis.sadd("setfail", userifo);
                            Thread.sleep(400);//模拟业务执行时间
                        }
    
                    } else {
                        System.out.println("用户:" + userifo + "抢购失败,库存以空");
                        jedis.sadd("setfail", userifo);
                        Thread.sleep(400);//模拟业务执行时间
                        // Thread.sleep(500);
                        return;
                    }
    
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    jedis.close();
                }
    
            }
    
        }
    }

       (2)jedis解决抢购的问题2:(解决了(1)的问题,并优于其性能)

    package com.thinkgem.jeesite;
    
    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.JedisPool;
    import redis.clients.jedis.JedisPoolConfig;
    
    import java.util.UUID;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * @DESC redis的list作为天然的库存队列。
     * 利用这一点,可以做一个list作为库存的公共资源,每次取值成功,就证明抢到商品了,否则,抢购失败
     * @Contact businessfgl@163.com
     */
    public class JedisMainTest2 {
        static final JedisPool pool = new JedisPool(new JedisPoolConfig() {{
            setMaxIdle(1000);
            setMaxTotal(1000);
            setTestOnBorrow(true);
        }}, "127.0.0.1", 6379);
    
        static final int stores = 1000;//库存量
        static final String storesName = "stores";//抢购商品存放仓库名称
        static final String storesCountKey = "storesCountKey";//记录以往仓库中存放商品的数量的KEY
        static final String setSuccessName = "setSuccessName";//记录抢购成功用户的set集合KEY
        static final String setFailName = "setFailName";//记录抢购失败用户的set集合KEY
    
    
        public static void main(String[] args) throws InterruptedException {
    
    
            ExecutorService executor = Executors.newFixedThreadPool(1000);
            ExecutorService executor1 = Executors.newFixedThreadPool(1000);
            Jedis jedis = pool.getResource();
    
            initStores();//初始化
            incrStores(10);//动态增加10个商品
            jedis.close();
    
            for (int i = 0; i < 1000; i++) {
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        for (int c = 0; c < 100; c++) {
    
                            executor1.execute(new MyRunnable());
                        }
                    }
                });
    //            Thread.sleep(50);
            }
            executor.shutdown();
    
    //        System.exit(1);
        }
    
        /*初始化库存*/
        public static void initStores() {
            Jedis jedis = pool.getResource();
    //        if(jedis.llen(storesName)>0){
    //            throw new RuntimeException("库存值大于0,不可初始化");
    //        }
    
            jedis.del(storesName, setSuccessName, setFailName);//删除仓库,删除抢购成功队列,删除抢购失败队列
            jedis.set(storesCountKey, stores + "");
            for (int i = 1; i <= stores; i++) {
                String storeProp = i + ":" + UUID.randomUUID().toString();//模拟   序号:ID
                jedis.rpush(storesName, storeProp);
            }
        }
    
        /*动态增加库存*/
        public static void incrStores(int stores) {
            Jedis jedis = pool.getResource();
            int storesCount = Integer.valueOf(jedis.get(storesCountKey));
            jedis.set(storesCountKey,(storesCount+stores)+"");//更新库存数量
            for (int i = 1 + storesCount; i <= stores + storesCount; i++) {
                String storeProp = i + ":" + UUID.randomUUID().toString();//模拟   序号:ID
                jedis.rpush(storesName, storeProp);
            }
        }
    
    
        static class MyRunnable implements Runnable {
            Jedis jedis = pool.getResource();
    
            public MyRunnable() {
            }
    
            @Override
            public void run() {
                try {
    
                    String userifo = UUID.randomUUID().toString();
    
                    //redis list取值
                    String ls = jedis.lpop(storesName);
    
                    //取值成功,说明拿到了商品,即抢购成功
                    if (ls != null) {
                        String[] er = ls.split(":");
                        int num = Integer.valueOf(er[0]);//商品序号
                        String ID = er[1];//商品ID
    
                        /* 抢购成功业务逻辑 */
                        jedis.sadd(setSuccessName, userifo);
                        System.out.println("用户:" + userifo + "抢购成功,当前抢购成功人数:"
                                + num + "------抢购成功商品ID:" + ID);
    
                        Thread.sleep(900);//模拟业务执行时间
    
                    } else {
                        System.out.println("用户:" + userifo + "抢购失败,库存以空");
                        jedis.sadd(setFailName, userifo);
                        Thread.sleep(400);//模拟业务执行时间
                        // Thread.sleep(500);
                        return;
                    }
    
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    jedis.close();
                }
    
            }
    
        }
    }
  • 相关阅读:
    Android笔记(ImageView、BaseLine、进度条ProgressBar)
    Android笔记(dp、sp、px、多选按钮CheckBox、单选按钮RadioButton)
    Android笔记(简介)
    Android Studio安装后Fetching android sdk component information超时的解决方案
    解决The environment variable JAVA_HOME does not point to a valid JVM installation
    使用Genymotiont调试出现错误INSTALL_FAILED_CPU_ABI_INCOMPATIBLE解决办法
    Android Studio导入第三方jar包及.so动态库
    数据库知识记录
    CentOS7.5下yum安装MySQL8.0.11笔记
    MyBatis的mapper.xml中判断集合的size
  • 原文地址:https://www.cnblogs.com/yuan951/p/7612473.html
Copyright © 2011-2022 走看看