synchronized 代码块测试用例

项目涉及到一个订单重复提交的问题,用一个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,进尔再加锁处理读写

原文地址:https://www.cnblogs.com/silyvin/p/9106816.html