synchronized 代码块测试用例

来源:互联网 发布:在淘宝上怎么开直播 编辑:IT博客网 时间:2019/10/14 18:16

项目涉及到一个订单重复提交的问题,用一个token验证来解决,

客户端订单页面请求一个token,此token由服务端生成,并加入缓存,客户端提交订单时将token一并传入,服务端验证token,下单时将token置为无效,以此来防止重复提交,因为每个token只有一次真正入库的机会

验证token的过程有两个最主要的程序:

读token,写token,这两个语句必须处在同步中


业务类:(假定为多线程单例)

public class ResourceService {    public SpResult buyResource(Map<String,String> map) {        String cacheToken = map.get("cacheToken");        SpResult sr = new SpResult();        if(cacheToken == null || "".equals(cacheToken)) {            sr.setHeaderMessage("您的token无效,请刷新页面。");        }  else {            synchronized (this) {                Long cacheNow = HandleCache.getCache(cacheToken);                if(cacheNow == null) {                    System.out.println("您已报过名,我们将在24小时之内联系您,请耐心等待。");                                    } else {                    Long now = System.currentTimeMillis();                    Long minus = now - cacheNow;                    if(minus > HandleCache.expireTime) {                        System.out.println("您的token已过期,请刷新页面。");                                            } else {                   //     resourceAccessor.buyResource(map);                        System.out.println("报名成功,我们将在24小时之内联系您。");                        HandleCache.destroyCache(cacheToken);                                            }                }            }        }        sr.setHeaderCode("200");        sr.setBody(null);        return sr;    }}

token缓存类:

public class HandleCache {    protected static Map<String,Long> cache = new ConcurrentHashMap<>();    public static Long expireTime = new Long(1000*60*5);    public static void setCache(String token) {        Long now = System.currentTimeMillis();        cache.put(token, now);    }    public static Long getCache(String token) {        Long temp = cache.get(token);        return temp;    }    public static void destroyCache(String token) {        cache.remove(token);    }    public static String makeToken() {        String base = "abcdefghijklmnopqrstuvwxyz0123456789";        Random random = new Random();        StringBuffer sb = new StringBuffer();        for (int i = 0; i < 20; i++) {            int number = random.nextInt(base.length());            sb.append(base.charAt(number));        }        return sb.toString();    }    public static void main(String[] args) {        String cacheToken = HandleCache.makeToken();        HandleCache.setCache(cacheToken);        Map<String,String> map = new HashMap<>();        map.put("phone", "13333333333");        map.put("targetId","0");        map.put("cacheToken",cacheToken);        long startMili=System.currentTimeMillis();        for(int i = 0; i < 10; i++){            MyThread thread = new MyThread(map);            thread.start();        }        long endMili=System.currentTimeMillis();        System.out.println("总耗时为:"+(endMili-startMili)+"毫秒");    }}

线程类:

class MyThread extends Thread{    private static ResourceService resourceService = new ResourceService();    private static Map<String,String> map;    public MyThread(Map<String,String> _map) {        map = _map;    }    public void run() {        resourceService.buyResource(map);    }}


主函数分十个线程请求,输出如下:

总耗时为:55毫秒
报名成功,我们将在24小时之内联系您。
您已报过名,我们将在24小时之内联系您,请耐心等待。
您已报过名,我们将在24小时之内联系您,请耐心等待。
您已报过名,我们将在24小时之内联系您,请耐心等待。
您已报过名,我们将在24小时之内联系您,请耐心等待。
您已报过名,我们将在24小时之内联系您,请耐心等待。
您已报过名,我们将在24小时之内联系您,请耐心等待。
您已报过名,我们将在24小时之内联系您,请耐心等待。
您已报过名,我们将在24小时之内联系您,请耐心等待。
您已报过名,我们将在24小时之内联系您,请耐心等待。


十个线程抢入库,仅有第一次真正入库。这里采用存放token的缓存对象cache和验证过程getCache+destroyCache 双重加锁,耗时55ms


如果拆掉getCache+destroyCache的过程锁,

    public SpResult buyResource(Map<String,String> map) {        String cacheToken = map.get("cacheToken");        SpResult sr = new SpResult();        if(cacheToken == null || "".equals(cacheToken)) {            sr.setHeaderMessage("您的token无效,请刷新页面。");        }  else {         //   synchronized (this) {                Long cacheNow = HandleCache.getCache(cacheToken);                if(cacheNow == null) {                    System.out.println("您已报过名,我们将在24小时之内联系您,请耐心等待。");                 //   sr.setHeaderMessage("您已报过名,我们将在24小时之内联系您,请耐心等待。");                } else {                    Long now = System.currentTimeMillis();                    Long minus = now - cacheNow;                    if(minus > HandleCache.expireTime) {                        System.out.println("您的token已过期,请刷新页面。");                  //      sr.setHeaderMessage("您的token已过期,请刷新页面。");                    } else {                   //     resourceAccessor.buyResource(map);                        System.out.println("报名成功,我们将在24小时之内联系您。");                        HandleCache.destroyCache(cacheToken);                   //     sr.setHeaderMessage("报名成功,我们将在24小时之内联系您。");                    }                }         //   }        }        sr.setHeaderCode("200");        sr.setBody(null);        return sr;    }



总耗时为:46毫秒
报名成功,我们将在24小时之内联系您。
报名成功,我们将在24小时之内联系您。
报名成功,我们将在24小时之内联系您。
报名成功,我们将在24小时之内联系您。
报名成功,我们将在24小时之内联系您。
报名成功,我们将在24小时之内联系您。
报名成功,我们将在24小时之内联系您。
报名成功,我们将在24小时之内联系您。
报名成功,我们将在24小时之内联系您。
报名成功,我们将在24小时之内联系您。


十个全抢到了,经过反复运行,也存在4个抢到,6个没抢到这样的情况,处于随机状态



3.12补充,全程加锁的模式,效率不高,下面参考单例模式,进行双重判断,第一次判断后加锁:


   public SpResult buyResource(Map<String,String> map) {        String cacheToken = map.get("cacheToken");        SpResult sr = new SpResult();        if(cacheToken == null || "".equals(cacheToken)) {            sr.setHeaderMessage("您的token无效,请刷新页面。");        }  else {            if(HandleCache.getCache(cacheToken) != null) {                synchronized (this) {                    Long cacheNow = HandleCache.getCache(cacheToken);                    if(cacheNow != null) {                        Long now = System.currentTimeMillis();                        Long minus = now - cacheNow;                        if (minus > HandleCache.expireTime) {                            System.out.println("您的token已过期,请刷新页面。");                            sr.setHeaderMessage("您的token已过期,请刷新页面。");                        } else {                            //     resourceAccessor.buyResource(map);                            System.out.println("报名成功,我们将在24小时之内联系您。");                            HandleCache.destroyCache(cacheToken);                            sr.setHeaderMessage("报名成功,我们将在24小时之内联系您。");                        }                    } else {                        System.out.println("您已报过名lock,我们将在24小时之内联系您,请耐心等待。");                        sr.setHeaderMessage("您已报过名lock,我们将在24小时之内联系您,请耐心等待。");                    }                }            } else {                System.out.println("您已报过名nlock,我们将在24小时之内联系您,请耐心等待。");                sr.setHeaderMessage("您已报过名nlock,我们将在24小时之内联系您,请耐心等待。");            }        }        sr.setHeaderCode("200");        sr.setBody(null);        return sr;    }

让一部分的线程不需要阻塞便可以进行下去,输出如下:

报名成功,我们将在24小时之内联系您。
您已报过名nlock,我们将在24小时之内联系您,请耐心等待。
您已报过名lock,我们将在24小时之内联系您,请耐心等待。
您已报过名lock,我们将在24小时之内联系您,请耐心等待。
您已报过名nlock,我们将在24小时之内联系您,请耐心等待。
您已报过名nlock,我们将在24小时之内联系您,请耐心等待。
您已报过名nlock,我们将在24小时之内联系您,请耐心等待。
您已报过名nlock,我们将在24小时之内联系您,请耐心等待。
您已报过名nlock,我们将在24小时之内联系您,请耐心等待。
总耗时为:14毫秒
您已报过名nlock,我们将在24小时之内联系您,请耐心等待。


Process finished with exit code 0


可以看到,有7个线程未阻塞,直接判断到null,证明已经处理掉了,2个线程判断到非null,进尔再加锁处理读写

0 0