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(); } } } }