我写了一个OrderMonitor的线程来监视数据库中未处理的单子,如果发现了未处理订单,就将它们取出来并将状态改为处理中,然后为每个订单创建一个线程进行发送new SendOrder(order),但不知道为什么有时偶尔会出现一个订单被处理两次的情况,出现的时间没有规律,我只测试到两次,各位有没有碰到过这种情况的?急!!!因为这个问题网站已经延迟发布,在线等!
public class OrderMonitor extends Thread {
public OrderMonitor() {
start();
} public void run() {
while (true) {
List list = null;
OrderControl oc = new OrderControl(); list = oc.getUncheckOrdersAndChangeState();//取出未处理订单,并将状态改为处理中
if (list != null && !list.isEmpty()) {
System.out.println(list.size()+"个未发订单!");
for (Object obj : list) {
Orders order=(Orders) obj;
                                        //处理订单
new SendOrder(order);
}
}
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

解决方案 »

  1.   

    如果单子多,sleep时间短,还没处理完,就进行了下一次循环处理,光更改状态还是有可能出现问题的加锁吧,只要当全部处理完后,才进行下一次循环处理
      

  2.   

    这是一段模拟火车站售票的多线程问题,你看下,对你应该有帮助class TicketsSystem
    {
    public static void main(String[] args)
    {
    SellThread st=new SellThread(); //设置线程
    new Thread(st).start();
    st.b=true;
    try
    {
    Thread.sleep(20); // 睡眠20毫秒
    }
    catch(Exception e)
    {
    e.toString();
    }
    new Thread(st).start();
    //new Thread(st).start();
    //new Thread(st).start();
    }
    }class SellThread implements Runnable
    {
    int tickets=100;
    boolean b=false;
    Object obj=new Object();
    public void run()
    {
    if(b==false)
    {
    while(true)
    sell();
    }
    else
    {
    while(true)
    {
    synchronized(obj)
    {
    if(tickets>0)
    {
    try
    {
    Thread.sleep(20); // 睡眠20毫秒
    }
    catch(Exception e)
    {
    e.toString();
    }
    System.out.println("obj:"+Thread.currentThread().getName()+"sell tickets:"+tickets);
    tickets--;
    }
    }
    }
    }
    }
    public synchronized void sell()
    {
    if(tickets>0)
    {
    try
    {
    Thread.sleep(20); // 睡眠20毫秒
    }
    catch(Exception e)
    {
    e.toString();
    }
    System.out.println("sell:"+Thread.currentThread().getName()+"sell tickets:"+tickets);
    tickets--;
    }
    }
    }
      

  3.   

    当时写SendOrder这个类的时候考虑到个别的大订单处理起来可能耗时较长,所以将它写成了线程,发现这个问题以后,我已经将这SendOrder改为一个常规的业务类,而现在只有OrderMonitor一个线程在操作数据库,诸位也看到了,在本次取得的未发订单处理完之前,是不会进行下一轮的操作的。但是现在的情况很奇怪,我先把SendOrder的的代码贴部分出来,
    public class SendOrder{
           Orders order;
           public SendOrder(Orders order) {
    this.order = order;
    startSend();
    }
           public void startSend() {
            System.out.println(order.getId()+"进入发送流程\t"+Global.DateFormator.format(new Date()));
             sendOrder();
             System.out.println(order.getId()+"发送完毕\t"+Global.DateFormator.format(new Date()));
             }
           private void sendOrder(){
             //..........处理订单
             }
    }然后再看控制台的输出:------------------------
    1个未发订单!
    订单106ready
    1个未发订单!
    订单106ready
    106进入发送流程 2008-01-10 16:33:42
    106进入发送流程 2008-01-10 16:33:42
    106发送完毕 2008-01-10 16:33:46
    106发送完毕 2008-01-10 16:33:50
    -------------------------真是摸不着头脑了!明明只有一个未发订单,怎么扫出两个来了,而且执行顺序也让人看不明白.......
    各位高手帮小弟多想想,明天周五要交活了。。
      

  4.   

    healer_kx :我猜想可能是有些操作要进行原子化处理。
    能否再说得具体点
      

  5.   

     restartrr:
    如果单子多,sleep时间短,还没处理完,就进行了下一次循环处理,光更改状态还是有可能出现问题的 加锁吧,只要当全部处理完后,才进行下一次循环处理 那么是在getUncheckOrdersAndChangeState()这里加锁还是在SendOrder里加锁?
    还是在别的什么是地方?我也曾经怀疑是这个问题,但这个Bug出现不规律,改了以后不知道是真正解决了还是暂时没有出现,头疼
      

  6.   

    你把
    sleep(1000);
    改成
    sleep(10000);时间改长点,你测试还会有重复的么?
      

  7.   

    在你的getUncheckOrdersAndChangeState()这个方法上加锁44看呢?我认为最好的办法是一次只取一个定单,这样数据同步相对简单些,性能上没多大影响.
      

  8.   

    参考一下吧
    public void run() {
    running: {
    while (!isAbort) {
    synchronized (this) {
    isPress = false;
    if (queue.isEmpty()) //queue是需要处理的任务队列
    try {
    wait();
    } catch (InterruptedException e) {
    }
    if (isAbort)
    break running;
    isPress = true;
    }
    doSMS(); //处理任务
    }
    }
    }
      

  9.   

    把时间改长点的话,首先会造成延迟,其次也不能从根本上解决问题。
    我又把getUncheckOrdersAndChangeState(就是刷数据库找订单的那个方法)加了锁,到现在为止还没有出现过那种情况,
    但我纳闷的是:明明只有一个线程,为什么控制台的输出却显示是有两个线程在调用这个方法?
      

  10.   

    sleep(1000); 
    改成 
    sleep(10000); 当然有延迟,前者是1秒,后者是10秒,差别大了去...至于出现重复调用,那应该是订单状态的控制被线程打乱了吧.
      

  11.   

    会不会因为你的构造函数里面调用了start方法,你多次定义了OrderMonitor对象,导致有多个线程啊?
      

  12.   

    我已经找到解决方案了,但还是很纳闷,我明明是只启动了一个线程,为什么会有两个呢?我再把问题描述一遍:
    class OrderMonitor extends Thread{
           OrderControl oc=new OrderControl();
          public OrderMonitor(){
                  start();
    }
          public void run(){
            oc.checkOrder();//处理订单
    }
    ////////////
    checkOrder(){
    System.out.println(Thread.currentThread().getName();//输出调用本方法的线程名称
    .......
    }
    我在Tomcat一启动的时候调用了一个叫Init的Servlet,然后在它的Init方法里创建了一个OrderMonitor的实例(只在
    这里创建一次),在checkOrder()里我输出了调用它的线程的名称,奇怪的是,居然出现了两个线程Thread-1和Thread-2
      

  13.   

    你创建一个Ordermonitor实例时启动了一个线程(因为你构造函数里面有个start方法),然后你再用这个实例
    启动一个线程。所以是两个线程。
      

  14.   

    shi8430419 ,我没有用这个实例启动别的线程啊?
      

  15.   

    Init里调用OrderMonitor(),而你的OrderMonitor下的start()启动了线程
      

  16.   

    索性你在OrderMonitor的构造方法里 new Throwable().printStackTrace(),看看到底是谁在创建这个线程对象。
      

  17.   

    显示是在Init里就创建了两个线程对象,但我的Init里就只有一行new OrderMonitor();啊,越来越迷糊了
      

  18.   

    可以确定Init这个Servlet的init方法被执行了两次,谁能告诉我这是为什么?有什么办法可以避免?
      

  19.   

    简直不敢相信,我找到了问题的根结。
    之前,我为了在浏览器里输入网址(不带工程名)不要进入Tomcat的默认目录,修改了Tomcat的配置文件
    conf/server.xml,在里面加了一行<Context path="" docBase="工程目录" debug="0" reloadable="true"></Context> ,这样就能进入我指定的工程名了,但这样就会把Init这个Servlet调用两次
    (Init设为随Tomcat自启动)。把Tomcat的配置文件改回来,问题就不再出现了,只有一个线程。
    OK!找个问题根源的感觉真好,但现在又得在IP后面跟上工程名了。谁有两全的方法?或者配置默认工程的其它方法。
    我的Tomcat是5.5,JDK 1.5,servlet 2.4
      

  20.   

    oc.getUncheckOrdersAndChangeState这个很明显是需要加锁的
      

  21.   

    oc.getUncheckOrdersAndChangeState这是个要加锁,但是如果只有一个线程调用它,还有必要吗?
    而且,我加了锁,也试过将它们放入同步块里,一样出现上面的问题。所以说很奇怪。
      

  22.   

    public OrderMonitor(){
    start();

    把这里的start();去掉调用的时候
    OrderMonitor() om = new OrderMonitor();
    om.start();不就行了。
      

  23.   

    你是程序起这个InitServlet吗?
    如果在web.xml中配置InitServlet自动启动,是不会出现Servlet被调用两次得情况得.
      

  24.   

    我猜是这样的,你的servlet是不是只启动了线程而没有销毁, 当设置reloadable="true",可能中间改过东西,应用自动部署过所以导致启动了2次
      

  25.   

    一任天然★IT民兵,你说的我都试过了,一样的结果。
    cz__wp ,InitServlet是在web.xml中配置的,
    baitianhai ,你说的我看不太懂,我曾把reloadable设为false,没有用,我的那个servlet确实是启动了线程并且没有销毁,我就是想要它一直保持活动,现在的情况是不是启动两次,而是有两个线程。不光是这个线程,任何在Init中的代码都被执行了两次!!我在Init中只写这么一句System.out.println("init...");结果输出了两个“init..."。很怪异!
    我猜大家也没遇到过这种情况,我在google上搜了半天,一天相关的信息都没有
      

  26.   

    > 简直不敢相信,我找到了问题的根结。
    > ……根据你的描述,看来你是把发布文件夹放在 Tomcat 的 webapps 里面了,这样做本身就已经告诉 Tomcat 这是一个 web-application 了。此时如果你再用一次 <Context ……>,那好像就真的是“部署了两个 web-application”了。我没这么做过,根据现象猜是这么回事儿。
      

  27.   

    在 Tomcat 里部署一个 web-application 有两种办法,一个办法是把发布目录拷贝到 webapps 文件夹下;另一个办法是在 web.xml 中用 <context ...> 指定。前一种方法方便,后一种方法“可配置性”更强。如果你是用前一种办法做部署,又想在 URL 里不使用“工程名”的话,可以把发布目录拷贝为 webapps/ROOT