synchronized 的一个问题,总是会出现重复

子木007 发布于 2012/04/19 15:16
阅读 1K+
收藏 0

程序中有一段代码用来生成一个序号,为了避免序号重复加了同步操作,代码如下:

public static final byte[] OBJ_LOCK = new byte[0];

@Transactional
private String getFamilyNo(Long villageId) {
    synchronized (OBJ_LOCK) {
        List<FamilyNo> familyNos = store.find("SELECT 省略部分", villageId);
        Integer rno;
        FamilyNo no;
        if (familyNos == null || familyNos.isEmpty()) {
            no = new FamilyNo();
            no.setVillageId(villageId);
            no.setNum(1);
            store.persist(context, no);
            rno = no.getNum();
        } else {
            no = familyNos.get(0);
            rno = no.getNum() + 1;
            no.setNum(rno);
        }
        return StringUtils.leftPad(String.valueOf(rno), 4, "0");
    }
}

然后在另一个 public 的方法中调用 getFamilyNo(xxx),  这个类是 singleton 的。

客户使用过程中,时不时会出现序号(getFamilyNo生成)重复的问题, 有点不解啊,请各位帮分析一下,是哪里写错了

以下是问题补充:

@子木007:我总觉得是同步没起作用, 如果起作用的话, 同步块中的代码有什么错误会导致出现重复? (2012/04/19 16:11)
@子木007:postgresql 的默认隔离级别是 : Read Committed (2012/04/19 16:45)
加载中
1
子木007
子木007

会不会是事务的原因呢? 因为是 Propagation.REQUIRED , 而导致线程1调用 getFamilyNo的外部事务还未提交,此时线程2拿到锁后读出来的还是未提交的数据, 于是……


0
草原小肥羊
草原小肥羊
返回值取决于 rno   如果两个序列号相同,你看看各自的rno是否相同。
0
草原小肥羊
草原小肥羊
比如  多次出现   familyNos ==  null   || familyNos.isEmpty()的情况,rno都为1
子木007
子木007
我咋总觉得是同步没起作用呢
子木007
子木007
不止是 1 会重复, 会重复多个数, 比如 2个 12 ,2个15, 截止现在遇到的都是重复2次。
0
DanielTo
DanielTo

返回是rno?

子木007
子木007
有问题? 请指出。
0
DanielTo
DanielTo

首先表是空的

第一次调用 getFamilyNo(1) 返回 1

第二次调用 getFamilyNo(2) 返回 1


 

子木007
子木007
这个结果是对的。要的结果是每次调用 getFamilyNo(1) 返回的都是增长后的结果 1/2/3... , 但现在偶尔会出现 getFamilyNo(1) 出现重复的结果比如 12/12/13/13
0
钩钩你的心
钩钩你的心
会不会是你的语句有问题。或是你把.get(0)换成.size()试试看
钩钩你的心
钩钩你的心
回复 @劳力没有士 : SQL语句
子木007
子木007
啥意思?
0
____33
____33

18            no.setNum(rno);

设置完之后没保存到数据库?

子木007
子木007
还在事务内, 属于 托管状态。 提交后会自动保存
0
DanielTo
DanielTo
调试一下吧,
子木007
子木007
不怕兄弟笑话, 单线程状态下我自己试了N多,没出现重复。 还不知怎么模拟多线程,再者我觉得多线程环境下加了断点,岂不是也相当于单线程了么。愚见
0
宏哥
宏哥

引用来自“劳力没有士”的答案

会不会是事务的原因呢? 因为是 Propagation.REQUIRED , 而导致线程1调用 getFamilyNo的外部事务还未提交,此时线程2拿到锁后读出来的还是未提交的数据, 于是……


APPROVED!
子木007
子木007
谢宏哥指点, 我试试 REQUIRES_NEW
0
Jeky
Jeky

目测代码没问题,写了个类似的,你看一下,然后再试试去掉同步锁

import java.util.*;

/**
 *
 * @author  Jeky
 */
public class SyncTest {

    private static final Object LOCK = new Object();
    private static final int WORKER_COUNT = 100;
    private static List<Integer> mockDB;
    private static Set<Integer> mockClient;

    public static void main(String[] args) {
        //init
        mockDB = Collections.synchronizedList(new LinkedList<Integer>());
        mockClient = Collections.synchronizedSet(new HashSet<Integer>());
        Thread[] workers = new Thread[WORKER_COUNT];
        for (int i = 0; i < WORKER_COUNT; i++) {
            workers[i] = new Thread(new Runnable() {

                @Override
                public void run() {
                    doBusiness();
                }
            }, "Worker " + i);
        }
        //start
        for (int i = 0; i < WORKER_COUNT; i++) {
            workers[i].start();
        }
    }

    private static void doBusiness() {
        for (int i = 0; i < 10000; i++) {
            synchronized (LOCK) {
                int result = 0;
                if (!mockDB.isEmpty()) {
                    result = mockDB.get(mockDB.size() - 1) + 1;
                }
                //mock persist
                mockDB.add(result);
                //mock client received result
                if (mockClient.contains(result)) {
                    System.out.println(Thread.currentThread().getName() + " has received dup result : " + result);
                    System.exit(1);
                } else {
                    System.out.println(Thread.currentThread().getName() + " has received result : " + result);
                    mockClient.add(result);
                }
            }
	    try {
	        Thread.sleep(10);
	    } catch (InterruptedException ex) {
	    }
        }
    }
}

子木007
子木007
兄弟动手真快, 谢过。
返回顶部
顶部