使用SingleThreadExecutor在任务之间等待

时间:2010-06-07 06:08:01

标签: java multithreading concurrency

我正在尝试(简单地)创建一个阻塞线程队列,当提交任务时,该方法会等待直到执行完毕。但困难的部分是等待。

这是我的12:30 AM代码,我认为是过度杀伤:

public void sendMsg(final BotMessage msg) {
    try {
        Future task;
        synchronized(msgQueue) {
            task = msgQueue.submit(new Runnable() {
                public void run() {
                    sendRawLine("PRIVMSG " + msg.channel + " :" + msg.message);
                }
            });
            //Add a seperate wait so next runnable doesn't get executed yet but
            //above one unblocks
            msgQueue.submit(new Runnable() {
                public void run() {
                    try {
                        Thread.sleep(Controller.msgWait);
                    } catch (InterruptedException e) {
                        log.error("Wait to send message interupted", e);
                    }
                }
            });
        }
        //Block until done
        task.get();
    } catch (ExecutionException e) {
        log.error("Couldn't schedule send message to be executed", e);
    } catch (InterruptedException e) {
        log.error("Wait to send message interupted", e);
    }
}

正如你所看到的,那里有很多额外的代码,只是让它在任务之间等待1.7秒。那里有更简单,更清洁的解决方案,还是这样呢?

7 个答案:

答案 0 :(得分:5)

好的,这是一个想法。您可以使用ScheduledExceutorService,它将记住您上次执行runnable并相应地延迟下一次执行,超过最大休眠时间(此处硬编码为1700)。

    //@GuardedBy("msgQueue")
Date mostRecentUpdate = new Date();

public void sendMsg(final BotMessage msg) {
    try {
        Future task;
        synchronized (msgQueue) {               
            long delta = new Date().getTime() - mostRecentUpdate.getTime();
            task = msgQueue.schedule(new Runnable() {
                public void run() {
                    sendRawLine("PRIVMSG " + msg.channel + " :" + msg.message);
                }
            }, delta <= 1700 ?1700 : 0, TimeUnit.MILLISECONDS);

            mostRecentUpdate = new Date();
        }
        // Block until done
        task.get();
    } catch (ExecutionException e) {
        log.error("Couldn't schedule send message to be executed", e);
    } catch (InterruptedException e) {
        log.error("Wait to send message interupted", e);
    }
}

答案 1 :(得分:3)

如果您的声明如下:

ExecutorService msgQueue = Executors.newSingleThreadExecutor();

您只需使用此代码即可实现您的目标:

msgQueue.submit(new Runnable() {
        public void run() {
            sendRawLine("PRIVMSG " + msg.channel + " :" + msg.message);
            try {
                Thread.sleep(Controller.msgWait);
            } catch (InterruptedException e) {
                log.error("Wait to send message interupted", e);
            }
        }
    })

作为单线程执行程序只能一次执行一个任务。

注意事项:

  1. 无需将提交同步到ExecutorService
  2. 如果您不需要执行结果,则不需要Future。
  3. 您对InterruptedException的处理有点麻烦。

答案 2 :(得分:1)

我不确定我是否理解你的目标:为什么你需要一个线程队列?你为什么不能把消息排队?

您可以使用生产者/消费者......让一个消费者读取队列,并使用多个生产者填充它。

更新

好的,这是一个更新版本,它会阻塞,直到消息排队并且每1700毫秒最多发送一条消息。

int delay = 1700; // milliseconds
StopWatch stopwatch = new Stopwatch();
BlockingQueue<BotMessage> msgQueue = new BlockingQueue<BotMessage>();

public void main()
{
    // A consumer thread
    new Thread(new Runnable() {
        public void run() {
            while(true)
            {
                // Blocks until there is something in the queue
                BotMessage msg = msgQueue.take();
                stopwatch.stop();

                // Sleeps until the minimum delay time has passed (if it hasn't passed)
                if(stopwatch.getElapsedTime() < delay)
                {
                    Thread.sleep(delay-stopwatch.getElapsedTime());
                }
                stopwatch.start();

                sendRawLine("PRIVMSG " + msg.channel + " :" + msg.message);
            }
        }
    })).start();
}

// Producers call sendMsg
public void sendMsg(final BotMessage msg) {
    msgQueue.put(msg);
}

您可以找到秒表here的实现。

答案 3 :(得分:1)

如果您愿意容忍从ThreadPoolExecutor继承,您可以定义一个新的Executor,它在完成每次执行后会延迟,如下所示(池中有一个线程,这会将任务的最大执行速率限制为每1700 ms) :

final ThreadPoolExecutor msgQueue = new ThreadPoolExecutor(1, 1,0L,TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()) {
        protected void afterExecute(Runnable r, Throwable t) {
            try {
                Thread.sleep(Controller.msgWait));
            } catch (InterruptedException e) {
                log.error("Wait to send message interrupted", e);
            }
        }
    };

然后按照正常情况使用它:

Future task = msgQueue.submit(new Runnable() {
        public void run() {
            sendRawLine("PRIVMSG " + msg.channel + " :" + msg.message);
        }
    });
    try {
        task.get();
    } catch (ExecutionException e) {
        log.error("Couldn't schedule send message to be executed", e);
    } catch (InterruptedException e) {
        log.error("Wait to send message interupted", e);
    }

我不一定认为它比使用ScheduledExecutorService更干净,尽管它确实避免了synchronized块,不得不在块之外声明未来并引入Date字段。

答案 4 :(得分:0)

虽然我不确定你为什么需要等待,因为我认为队列实现将按照提交的顺序选择任务,即它将是FIFO,因此任务应该被执行(当你调用msgQueue.get()时)按照提交的顺序。如果这不是行为,请纠正我。

但是,如果我们仍然需要暂停提交帖子,我们可以按照以下说明进行操作。

你是否可以在将msg提交到队列而不是将等待任务提交到队列之后直接让线程进入休眠状态?

所以它将是

synchronized(msgQueue) {
            task = msgQueue.submit(new Runnable() {
                public void run() {
                    sendRawLine("PRIVMSG " + msg.channel + " :" + msg.message);
                }
            });
            Thread.sleep(Controller.msgWait); // no other thread can submit a new task until this sleep is over.
        }

答案 5 :(得分:0)

而不是未来使用CountDownLatch

基本上:

public void sendMsg(final BotMessage msg) {
    try {
        final CountDownLatch latch = new CountDownLatch(1);
        msgQueue.submit(new Runnable() {
            public void run() {
                sendRawLine("PRIVMSG " + msg.channel + " :" + msg.message);
                latch.countDown();
                try {
                    Thread.sleep(Controller.msgWait);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); //This is usually a best practice
                    log.error("Wait to send message interupted", e);
                }
            }
        });
        //Block until done
        latch.await();
    } catch (ExecutionException e) {
        log.error("Couldn't schedule send message to be executed", e);
    } catch (InterruptedException e) {
        log.error("Wait to send message interupted", e);
    }
}

答案 6 :(得分:0)

根据建议here,我发布这个作为答案,因为它是我提出的最干净的解决方案,但不是这里发布的任何答案的实际副本。

感谢所有帮助

 public final CustBlockingQueue<BotMessage> msgQueue = new CustBlockingQueue<BotMessage>(1);


     // A consumer thread
     new Thread(new Runnable() {
         public void run() {
             while (true)
                 try {
                     // Blocks until there is something in the queue
                     BotMessage msg = msgQueue.take();
                     sendRawLine("PRIVMSG " + msg.getChannel() + " :" + msg.getMessage());
                     //Release lock so that put() unblocks
                     msgQueue.lock.lockInterruptibly();
                     msgQueue.doneProcessing.signal();
                     msgQueue.lock.unlock();
                     //Wait before continuing
                     Thread.sleep(Controller.msgWait);
                 } catch (InterruptedException e) {
                     log.error("Wait for sending message interrupted", e);
                 }
         }
     }).start();

 public class CustBlockingQueue<E> extends ArrayBlockingQueue<E> {
     public ReentrantLock lock = new ReentrantLock(false);
     public Condition doneProcessing = lock.newCondition();

     public CustBlockingQueue(int capacity) {
         super(capacity);
     }

     public void put(E e) throws InterruptedException {
        lock.lockInterruptibly();
        super.put(e);
        doneProcessing.await();
        lock.unlock();
    }
}