我有一个函数,提供给客户调用
这个函数内容是发短信,然后扣除客户对应的费用,取客户当前余额减发送数.问题出来了:
客户写了多线程的程序调用这个函数发短信,取客户当前余额经常被取重复.
请问,我要怎么样改我的这个函数才能实现线程同步!(我这样描叙不知道大家能不能看懂,如果需要进一步说明请留言在后面)
一定结帖!

解决方案 »

  1.   

    使用了synchronized关键字就可以解决多线程共享数据同步问题。这个就是银行取钱的范例,学Java肯定要碰到的,看来基础还得好好打结实噢。  synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。
      

  2.   

    synchronized public int sendMessage(String userid,String msgText) 
    {
       if (无效短信) return -1;
       发送;
       余额=余额-发送条数;
       return 1;}我这样加了synchronized ,但今天测下来问题还是有之前的问题。
      

  3.   

    public int sendMessage(String userid,String msgText) 
    { synchronized (余额) {
      if (无效短信) return -1; 
      发送; 
      余额=余额-发送条数; 
      return 1; 
      }

      

  4.   

    由于我余额是users.getMoney_remain(),所以我写成了以下代码
    现在看来发送和扣费还是有问题,我再查查日志,看问题出在哪??
    public int sendMessage(String userid,String msgText) 

      if (无效短信) return -1; 
      synchronized (users) {
        发送; 
        余额=余额-发送条数; 
        log.info("余额-发送条数"+余额+"-"+发送条数)
        return 1; 
      }
      

  5.   

    我觉得这样也许更好些:
    在user里加一个方法synchronized calcMoney_remain(){
    余额=余额-发送条数;
    }
    当然getMoney_remain()也要同步。
    这样就不用再sendMessage()里计算余额了,而是让user类负责计算自己的余额。
    sendMessage()也就不需要同步了。
      

  6.   

    2008-10-21 09:27:46,448 [service.BusService]-[INFO] |Modify users
    2008-10-21 09:27:46,468 [service.BusService]-[INFO] |Modify users
    2008-10-21 09:27:46,476 [service.BusService]-[INFO] |余额-发送条数184-1                《--问题在这里  
    2008-10-21 09:27:46,501 [service.BusService]-[INFO] |余额-发送条数184-1                《--问题在这里我不知道
    public int sendMessage(String userid,String msgText) 

      if (无效短信) return -1; 
      synchronized (users) { 
        发送; 
        余额=余额-发送条数; 
        log.info("余额-发送条数"+余额+"-"+发送条数) 
        return 1; 
      } 

    为什么实际上users这个资源为什么没有被同步,还出现了线程问题。
      

  7.   

    不是太清楚你的代码到底是怎么样的。
    有没有可能是sendMessage()每次都是根据userid来new一个user?
    那synchronized (users) 就没用了。
      

  8.   


         public int sendMessage(String account, String password, String destmobile,
                String msgText) {        log.info("Begin sendMessage");
            int taskID = Utils.getTaskID();
            try {
                SimpleDateFormat dateformat = new SimpleDateFormat(
                        Constants.DATE_TIME_FORMAT);
                int userid = users.getUser_id();
                double price = getPrice(account);
                for (int i = 0; i < msgCount; i++) {
                    String msg = msgText.substring(0, Math.min(maxChars, msgText
                            .length()));
                 String beginSendDate = dateformat.format(new Date());
                 Users users = daoService.getUserByAccount(account);
                 synchronized (users){                
                     String ispTaskId = adapte.sendMessage(destmobile, msg);
                     boolean successSend = true;
                     try 
                     {
                         long rt = Long.parseLong(ispTaskId.substring(0, Math.min(5, ispTaskId.length())));
                         log.info("|rt" + rt);
                         if (rt < 0) {    
                             successSend = false;
                        }
                     }catch (Exception ex) {
                      log.info("Exception ex");
                         successSend = false;  
                     }
                    
                     if (!successSend) 
                     {
                         return -3;
                        }                 String endSendDate = dateformat.format(new Date());                
                     if (msgText.length() > maxChars) {
                         msgText = msgText.substring(maxChars, msgText.length());
                     }
    }             log.info("|Modify users");
    users = daoService.getUserByAccount(account);
    log.info("|remain_money="+users.getMoney_remain()+"-"+msgCount);
                 users.setMoney_remain(users.getMoney_remain()  - msgCount);             //这里是改费用
                 daoService.modifyUser(users);
             }
            } catch (Exception ex) {
                log.info("|SendMessage error: " + ex);
                if (ex instanceof java.lang.IndexOutOfBoundsException)
                 return -88;
                else
                    return -9;
            }     
            return taskID;
        }
      

  9.   

    代码在楼上,大家帮看看,还是没能使users类中的money_remain线程同步。
      

  10.   

    现在的确是sendMessage()每次都是根据userid来new一个user
    看样子问题在这里了怎么改,给个方案,哥们!!
      

  11.   

    无论new了多少个user,他们的id应该是一样的吧?如果这个成立的话,那么可以这样:
    HashMap<String, Object> userLockTable = new HashMap<String, Object>();
    synchronized (userLockTable) {
    if (!userLockTable.containsKey(account)) {
    userLockTable.put(account, new Object());
    }
    }
    Object lock = userLockTable.get(account);
    synchronized (lock) {
    //...
    }
    当然还有一些细节要考虑,比如是否要定时clear这个表免得它越来越大。
    总之就是为每个userid分配锁,而不是对于user对象。
      

  12.   

    我是说根据id new出来的多个的user对象,他们的id应该是一样的吧?
      

  13.   

    ID一样的,就是account,
    用单态可以么?
      

  14.   

    但是你的user不是DAO返回的吗?
    Users users = daoService.getUserByAccount(account);
    如果你可以做到一个id只有一个user,那用原来的synchronized (users) 应该是可以正确同步的。
    否则的话,可以用我15楼的方法.用synchronized (lock) {
                //...
            }代替synchronized (users)
      

  15.   

    Quote=引用 15 楼 xstom19 的回复:]
    无论new了多少个user,他们的id应该是一样的吧?如果这个成立的话,那么可以这样: Java codeHashMap<String, Object> userLockTable = new HashMap<String, Object>();
            synchronized (userLockTable) {
                if (!userLockTable.containsKey(account)) {
                    userLockTable.put(account, new Object());
                }
            }
            Object lock = userLockTable.get(account);
       …
    [/Quote]用了这段代码,测试,结果还是有没同步的问题public int sendMessage(String account, String password, String destmobile,
                String msgText) {
            try {
             HashMap<String, Object> userLockTable = new HashMap<String, Object>();
            synchronized (userLockTable) {
                if (!userLockTable.containsKey(account)) {
                    userLockTable.put(account, new Object());
                }
            }
            Object lock = userLockTable.get(account);
            synchronized (lock) {
                Users users = daoService.getUserByAccount(account);
                int userid = users.getUser_id();
                发送;
                  users.setMoney_remain(users.getMoney_remain()  - msgCount);             //这里是改费用
                  daoService.modifyUser(users);
             }
            } catch (Exception ex) {
                log.info("|SendMessage error: " + ex);
                if (ex instanceof java.lang.IndexOutOfBoundsException)
                    return -88;
                else
                    return -9;
            }     
            return taskID;
        }     }
      

  16.   

    TO xstom19 :再帮忙看看,我觉得问题就快要解决了。
      

  17.   


    那是因为你是在sendMessage()里new了userLockTable。
    这样每次调用sendMessage()当然就生成一个新的hash表了。
    HashMap<String, Object> userLockTable = new HashMap<String, Object>();
    要放在sendMessage()的外面,至于是不是static的,就取决于你的泪是什么样的。
      

  18.   

    to xstom19 :现在情况是我的客户他调用sendMessage()这个函数,
    我放在外面怎么放?
      

  19.   

    我不能强制他们调用sendMessage()之前作HashMap<String, Object> userLockTable = new HashMap<String, Object>();呀
    明白我意思么?
    分我一会再加,帖子一定结,谢谢大家的帮忙了,特别是xstom19 
      

  20.   

    等于这个sendMessage()函数是我提供给我的客户做2次开发的接口函数
    他们调用这个就可以发信息了,不用管里面的内容
    但现在我的这个函数没有作到线程同步,有很大问题
    我可以改函数里的东西,但函数的头不能改,也不能把函数包含在别的函数里,那样没效果呀的。
      

  21.   

    我的意思是把userLockTable作为一个类的成员变量,
    就像这样:public class SomeClass{
    private HashMap <String, Object> userLockTable = new HashMap <String, Object>();
    public int sendMessage(String account, String password, String destmobile,
                String msgText){
    //...
    }
    }
      

  22.   

    来晚了
    其实我想说的的是数据库加个sequence的字段
    取得时候字段一道取出来
    比如说是1
    然后提交的时候判断下字段是否为1
    不是回滚
    是1的话提交并且字段自增1
      

  23.   

    按你说的这样了,还是同步问题。public class BusinessService {    private static Log log = LogFactory.getLog(BusinessService.class);
        
        private SMSDaoService daoService = SMSDaoServiceFactory.getDaoService();
        private HashMap <String, Object> userLockTable = new HashMap <String, Object>();
        
    public int sendMessage(String account, String password, String destmobile,
                String msgText) {
            try {
            synchronized (userLockTable) {
                if (!userLockTable.containsKey(account)) {
                    userLockTable.put(account, new Object());
                }
            }
            Object lock = userLockTable.get(account);
            synchronized (lock) {
                Users users = daoService.getUserByAccount(account);
                int userid = users.getUser_id();
                发送;
                  users.setMoney_remain(users.getMoney_remain()  - msgCount);             //这里是改费用
                  daoService.modifyUser(users);
             }
            } catch (Exception ex) {
                log.info("|SendMessage error: " + ex);
                if (ex instanceof java.lang.IndexOutOfBoundsException)
                    return -88;
                else
                    return -9;
            }     
            return taskID;
        }     }
    }
      

  24.   

    我写了个程序10个线程,每个发10条
    结果扣费只扣了88
    还有12估计是没同步
    具体要查日志,应该就是这个同步问题,我查了N次了另外,如果代码没错,那是不是TOMCAT内存没清,新代码没生效的原因
    是不是要删掉tomcat 的WORK目录再试下??
      

  25.   

    我写了小程序测了一下,能正确同步的阿。
    public class Main2 {
    private HashMap<String, Object> userLockTable = new HashMap<String, Object>(); public int sendMessage(String account) {
    synchronized (userLockTable) {
    if (!userLockTable.containsKey(account)) {
    userLockTable.put(account, new Object());
    }
    }
    Object lock = userLockTable.get(account);
    synchronized (lock) {
    User user = new User();
    int c = user.getCount();
    c = c + 1;
    user.setCount(c);
    System.out.println(Thread.currentThread().toString() + ":" + user.getCount());
    }
    return 0;
    } public static void main(String[] args) {
    final Main2 main = new Main2();
    for (int i = 0; i < 10; i++) {
    Thread thread = new Thread(new Runnable(){ @Override
    public void run() {
    while(true) {
    main.sendMessage("");
    try {
    Thread.sleep(10);
    } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    } } });
    thread.start();
    }
    }}class User{
    private static int COUNT = 0; public int getCount() {
    return COUNT;
    } public void setCount(int count) {
    COUNT = count;
    }}
    如果你刷新了work目录还不行得话,那问题可能就出在你是怎么调用BusinessService的sendMessage方法了。
    不过你的BusinessService应该是单例的吧,如果BusinessService也是被new了多个的话,那要把userLockTable 声明成static的。
      

  26.   

    你的小程序我运行了,的确是同步的。
    我那个我把work目录删除后,计算费用还是不对,
    我现在看日志,看是不是同步的问题
    等晚点我加了分再结帖,毕竟花了那么多时间,不加不厚道。按道理这个小程序和我的程序是一模一样的机制了,应该是不回有问题了呀!
    我先查日志吧。
      

  27.   

    to xstom19 :我把userLockTable 声明成static的,然后测试就通过了,线程始终可以正确同步,谢谢你。总结:
    问题到这里基本结束了,从这个问题中我向大家学到了很多新知识,
    谢谢大家的热心帮忙,以后我也要多来CSDN答帖子,来提高自己
    因为要一天以后才能加分,所以明天结帖.