唤醒睡眠线程 - 中断()与将睡眠“分裂”为多个睡眠

时间:2012-01-03 05:57:21

标签: java android multithreading sleep

这个要求出现在我的Android应用程序中,但它一般适用于Java。我的应用程序每隔几秒就“做一些事”。我已经实现了如下(只是相关的片段 - 不是完整的代码):

Snippet1:

public class PeriodicTask {

    private boolean running = true;
    private int interval = 5;

    public void startTask(){
        while (running){
            doSomething();
            try{
                Thread.sleep(interval * 1000);
            } catch(InterruptedException e){
                //Handle the exception.
            }
        }
    }

    public void stopTask(){
        this.running = false;
    }

    public void setInterval(int newInterval){
        this.interval = newInterval;
    }
}

正如您所看到的,这种方法的问题是setInterval()不会立即生效。它仅在前一次sleep()完成后生效。

由于我的用例允许最终用户以固定步长(1秒 - 从1到60秒)设置间隔,因此我将实现修改为在循环内休眠;并按如下方式每秒检查新的间隔值:

Snippet2:

public class PeriodicTask {

    private boolean running = true;
    private int interval = 5;
    private int loopCounter = 0;

    public void startTask(){
        while (running){
            doSomething();
            try{
                while(loopCounter < interval) {
                    Thread.sleep(1 * 1000);
                    loopCounter ++;
                }
            } catch(InterruptedException e){
                //Handle the exception.
            }
        }
    }

    public void stopTask(){
        this.running = false;
    }

    public void setInterval(int newInterval){
        synchronized (this) {
            this.interval = newInterval;
            if(newInterval < loopCounter){
                loopCounter = 0;
            }
        }
    }
}

有没有理由不使用这种方法?

我最近为此目的遇到了interrupt()方法。但是,我无法弄清楚如何使用它。例如,与睡眠方法不同,中断方法不是static。那么,我会中断Thread什么?

public void setInterval(int newInterval){
        this.interval = newInterval;
        //What thread do I call interrupt() on?
    }

其次,如果我成功中断了休眠Thread,我相信catch的{​​{1}}块将被执行。但是,此时我需要再次调用InterruptedException。关于这个递归的终止,我很困惑。关于中断()的使用,我已经经历了几个关于SO的问题,但无法弄清楚有什么能帮到我。

任何指针?


编辑 - 有关确切要求的更多详细信息:

我的应用每隔几秒钟使用一次REST调用获取一些值。更新间隔可由用户配置。

现在,请说更新间隔已设置为60秒。我发布的Snippet1工作(错误)如下:

  • 线程进入睡眠状态60秒。
  • 现在,假设用户将更新间隔更改为5秒。线程还在睡觉。
  • 只有在60秒过期后,startTask()才能看到新的更新间隔。

确切的要求是新的更新间隔应该立即生效(或者至少在设置后不迟于1秒 - 因为这是用户可能会感知到的)。

我的Snippet2和Snippet3试图达到这个要求。

4 个答案:

答案 0 :(得分:11)

IIRC,在Java中,你可以使用超时来object.wait()。这不是你想要的吗?如果要更改另一个线程的超时,请更改一些'waitValue'变量并通知()。然后线程将“立即”运行,然后再次使用新的超时值。不需要明确的睡眠。

答案 1 :(得分:7)

This回答帮助我完成了这项工作。发布一些关于我如何实现它的代码。特别重要的是startTask()setInterval()

public class PeriodicTask {

    private volatile boolean running = true;
    private volatile int interval = 5;
    private final Object lockObj = new Object();

    public void startTask() {
        while (running) {
            doSomething();
            synchronized (lockObj) {
                try{
                    lockObj.wait(interval * 1000);
                } catch(InterruptedException e){
                    //Handle Exception
                }
            }
        }
    }

    public void stopTask() {
        this.running = false;
    }

    public void setInterval(int newInterval) {
        synchronized (lockObj) {
            this.interval = newInterval;
            lockObj.notify();
        }
    }
}

答案 2 :(得分:5)

我不清楚你真正想做什么。你的目标是停止在PeriodicTask中运行循环的线程,还是只想打破循环并允许线程继续?如果您只想打破循环但允许线程继续,请考虑以下示例:

public class ThreadStopExample {

    public static void main ( String[] args ) throws InterruptedException {
        final PeriodicTask task = new PeriodicTask ();
        Thread t = new Thread ( new Runnable () {
            @Override
            public void run () {
                System.out.println ( Thread.currentThread ().getName () 
                    + " starting" );
                task.startTask ();
                System.out.println ( Thread.currentThread ().getName ()
                    + " done with the periodic task" );
            }
        } );
        t.start ();
        Thread.sleep ( 12000 );
        task.setInterval ( 1 );
        Thread.sleep ( 3000 );
        task.stopTask ();
    }

    static class PeriodicTask {

        private volatile boolean running = true;
        private volatile int interval = 5;

        public void startTask () {
            running = true;
            while ( running ) {
                doSomething ();
                try {
                    int count = 0;
                    while ( running && count++ < interval ) {
                        Thread.sleep ( 1000 );
                    }
                } catch ( InterruptedException e ) {
                    Thread.currentThread ().interrupt ();
                    running = false;
                    break;
                }
            }
        }

        public void stopTask () {
            running = false;
        }

        public void setInterval ( int newInterval ) {
            interval = newInterval;
        }

        private void doSomething () {
            System.out.println ( "[" + Thread.currentThread ().getName () 
                + "] Interval: " + interval );
        }
    }
}

这与您现有的代码非常相似。请注意volatile字段,以确保运行PeriodicTask循环的线程与尝试更改间隔和停止任务的主线程之间的正确同步(请参阅here以获取有关java内存模型的更多信息的链接)。如您所见,在调用停止任务之后,继续使用PeriodicTask实例的线程。另请注意,PeriodicTask在收到中断的异常时会调用当前线程的中断。这确保在当前线程上设置中断标志,以便任何外部代码能够看到中断并做出适当的反应,例如,而不是打印完成,运行PeriodicTask的线程可能已经检查了自身的中断状态并做了一些有趣的事情。

如果您的目标是停止线程本身,那么您可能希望使用PeriodicTask扩展Thread,除非您有充分的理由这样做,否则不推荐使用,或者让PeriodicTask实现Runnable。考虑下一个例子:

public class ThreadStopExample2 {

    public static void main ( String[] args ) throws InterruptedException {
        final PeriodicTask task = new PeriodicTask ();
        Thread t = new Thread ( task );
        t.start ();
        Thread.sleep ( 12000 );
        task.setInterval ( 1 );
        Thread.sleep ( 3000 );
        t.interrupt ();
    }

    static class PeriodicTask implements Runnable {

        private volatile int interval = 5;

        @Override
        public void run () {
            while ( true ) {
                doSomething ();
                try {
                    int count = 0;
                    while ( count++ < interval ) {
                        Thread.sleep ( 1000 );
                    }
                } catch ( InterruptedException e ) {
                    Thread.currentThread ().interrupt ();
                    break;
                }
            }
        }

        public void setInterval ( int newInterval ) {
            interval = newInterval;
        }

        private void doSomething () {
            System.out.println ( "[" + Thread.currentThread ().getName () 
               + "] Interval: " + interval );
        }
    }
}

这里,PeriodicTask处于忙循环状态,直到运行它的线程被中断。中断用于指示PeriodicTask退出循环,然后允许线程在run方法结束时完成。

关于你的两个明确的问题:不,如果你不打算控制执行线程,我没有看到使用PeriodicTask的任何实际问题,例如:也许PeriodicTask的实例由池中的线程运行(但确保修复您的代码以便正确同步),并且,当使用中断时,您可以在要中断的线程的实例上调用它。如何获得对该线程的引用取决于您的系统。

答案 3 :(得分:1)

你在正在休眠的线程上调用interrupt(),它将抛出你在catch块中处理的InterruptedException。然后你有了新的间隔,你可以循环然后回去睡觉。如果你抓住并处理InterruptedException没有进一步发生。

让我提供一些创建和中断线程的示例链接,从您的评论中您似乎缺少一些重要的想法。请仔细阅读这些内容,然后您应该了解执行所要求的标准方法。

http://docs.oracle.com/javase/tutorial/essential/concurrency/simple.html http://docs.oracle.com/javase/tutorial/essential/concurrency/interrupt.html