synchronized(java高并发下的唯一性验证)

一般会遇到唯一性的问题,比如添加用户要求用户名称或登陆名唯一,我们通常的做法是:先根据条件去数据中查询是否存在,如果存在则提示已经存在了,不允许添加,否则插入。

但是这种做法在两种情况下很容易出现问题:

1. 当添加用户这个过程耗时比较长时,如果两个人同时添加了一个相同名称的用户,低并发的情况下容易两个验证都通过。比如:第一个添加还没有进入数据库,第二个已经通过了验证,准备执行添加操作时。

2.当添加用户这个过程耗时较短,但是是高并发的情况下,很容易添加多个相同的用户。

查找了网上多个资料后发现以下几种方法和实验过程是比较有价值的,如下:

下面是一个简单的例子, 向表t_test_curr插入数据,t_test_curr表包含两个字段,一个id(主键,自增长),一个username,要求唯一

1 .不考虑并发性的做法:

    public void testConcurr (String username) {
        //t_test_curr并发测试表名
        String uniqueSql = new StringBuilder("select 1 from t_test_curr where username = ?").toString();
        List<SerializableJSONObject> uniqueList = this.baseDao.sqlQueryResult4Cache(uniqueSql, new Object[]{username});
        if (uniqueList != null && uniqueList.size() > 0) {
            throw new OperateFailureException("用户名重复!");
        }
        String saveSql = new StringBuilder("insert into t_test_curr(username) values (?)").toString();
        this.baseDao.executeDdlSql(saveSql, new Object[]{username});
    }

测试代码:

class TestConThread implements Runnable {

    private ActivityService activityService;

    TestConThread (ActivityService activityService) {
        this.activityService = activityService;
    }

    @Override
    public void run () {
        activityService.testConcurr("malone");
    }
}

    public static void main (String[] args) throws Exception{
        ApplicationContext ctx = BaseTest.getCtx();
        ActivityService activityService = (ActivityService)ctx.getBean("activityService");
        TestConThread testConThread = new TestConThread(activityService);
        Thread[] threads = new Thread[10];
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(testConThread);
            threads[i].start();
        }
        for (int i = 0; i < 10; i++) {
            threads[i].join();
        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }

用上面的代码测试,启动十个线程,数据库中插入了9条记录,所以这种代码在并发的情况下没有任何抵抗能力,代码中打印出来的结果为:2582

2 使用锁,锁上当前方法:

    public void testConcurr2 (String username) {
        lock.lock();
        try {
            String uniqueSql = new StringBuilder("select 1 from t_test_curr where username = ?").toString();
            List<SerializableJSONObject> uniqueList = this.baseDao.sqlQueryResult4Cache(uniqueSql, new Object[]{username});
            if (uniqueList != null && uniqueList.size() > 0) {
                throw new OperateFailureException("用户名重复!");
            }
            String saveSql = new StringBuilder("insert into t_test_curr(username) values (?)").toString();
            this.baseDao.executeDdlSql(saveSql, new Object[]{username});
        } finally {
            lock.unlock();
        }
    }

上面的线程类的run方法调用当前方法,这样数据不会重复,只会有一条,但是用锁会对吞吐量造成一定的影响,代码打印结果:3683,从打印结果来看,基本上慢了1/3

3 使用并发集合:

    public void testConcurr1 (String username) {
        if (isInCache(username)) {
            throw new OperateFailureException("你输入的用户名已经存在!");
        }
        String uniqueSql = new StringBuilder("select 1 from t_test_curr where username = ?").toString();
        List<SerializableJSONObject> uniqueList = this.baseDao.sqlQueryResult4Cache(uniqueSql, new Object[]{username});
        if (uniqueList != null && uniqueList.size() > 0) {
            throw new OperateFailureException("用户名重复!");
        }
        String saveSql = new StringBuilder("insert into t_test_curr(username) values (?)").toString();
        this.baseDao.executeDdlSql(saveSql, new Object[]{username});
        removeFromCache(username);
    }

    public boolean isInCache (String username) {
        if (map.containsKey(username)) {
            return true;
        } else {
            map.put(username, username);
            return false;
        }
    }

    public void removeFromCache (String username) {
        map.remove(username);
    }

上面的代码逻辑为:在service中放一个ConcurrentHashMap,当请求来时,就把username和map里的数据比较,如果重复,就直接提示重复,如果不重复就放入的集合中,然后在验证在数据库中是否重复,如果重复则提示,不重复则插入,并移除并发集合中的username;这样做的好处时,不会锁住代码,系统吞吐量不会受影响,而且在并发环境下不会出现重复数据,测试代码打印结果:2459
 

展开阅读全文

没有更多推荐了,返回首页