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、实际应用: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(); } } } }
时间: 2024-10-19 15:38:04