【问题标题】:Cleaning up after two threads finish execution两个线程完成执行后清理
【发布时间】:2015-09-15 15:24:51
【问题描述】:

我有一个应用程序正在运行每个作业需要两个线程的作业。这两个线程通常会做一些工作并在彼此之后不久完成。然后在第二个线程完成后,我需要进行一些清理,但由于线程正在执行一些网络 IO,因此一个线程可能会被阻塞很长时间。在这种情况下,我希望在第一个线程完成后几秒钟进行清理。

我在回调类中使用以下代码实现了此行为:

private boolean first = true;

public synchronized void done() throws InterruptedException {
    if (first) {
        first = false;
        wait(3000);
        // cleanup here, as soon as possible
    }
    else {
        notify();
    }
}

两个线程在完成时都会调用 done() 方法。然后第一个将在 wait() 中最多阻塞 3 秒,但当秒线程调用 done() 方法时会立即通知。

我已经测试了这个实现,它似乎运行良好,但我很好奇是否有更好的方法来做到这一点。尽管这个实现看起来并不太复杂,但我担心我的程序会死锁或出现一些意想不到的同步问题。

【问题讨论】:

  • 线程 1 在里面时,线程两个访问 done() 怎么办?
  • @nafas - wait 释放锁。
  • @OldCurmudgeon 啊哈哈是的,出于某种原因,我在考虑 Thread.sleep(...)。我的坏...

标签: java multithreading synchronization


【解决方案1】:

我希望我了解您的需求。您想等待线程 a 完成,然后等待 3 秒或线程 b 结束。

最好使用较新的 Concurrent 工具而不是旧的 wait/notify,因为它们有很多边缘情况。

// Two threads running so count down from 2.
CountDownLatch wait = new CountDownLatch(2);

class TestRun implements Runnable {

    private final long waitTime;

    public TestRun(long waitTime) {
        this.waitTime = waitTime;
    }

    @Override
    public void run() {
        try {
            // Wait a few seconds.
            Thread.sleep(waitTime);
            // Finished! Count me down.
            wait.countDown();
            System.out.println(new Date() + ": " + Thread.currentThread().getName() + " - Finished");
        } catch (InterruptedException ex) {
            System.out.println(Thread.currentThread().getName() + " - Interrupted");
        }
    }

}

public void test() throws InterruptedException {
    // ThreadA
    Thread threadA = new Thread(new TestRun(10000), "Thread A");
    // ThreadB
    Thread threadB = new Thread(new TestRun(30000), "Thread B");
    // Fire them up.
    threadA.start();
    threadB.start();
    // Wait for all to finish but threadA must finish.
    threadA.join();
    // Wait up to 3 seconds for B.
    wait.await(3, TimeUnit.SECONDS);
    System.out.println(new Date() + ": Done");
    threadB.join();
}

打印愉快:

Tue Sep 15 16:59:37 BST 2015: Thread A - Finished
Tue Sep 15 16:59:40 BST 2015: Done
Tue Sep 15 16:59:57 BST 2015: Thread B - Finished

已添加

有了新的明确性 - 任何线程的结束都会启动计时器 - 我们可以使用第三个线程进行清理。每个线程完成后必须调用一个方法来触发清理机制。

// Two threads running so count down from 2.
CountDownLatch wait = new CountDownLatch(2);

class TestRun implements Runnable {

    private final long waitTime;

    public TestRun(long waitTime) {
        this.waitTime = waitTime;
    }

    @Override
    public void run() {
        try {
            // Wait a few seconds.
            Thread.sleep(waitTime);
            // Finished! Count me down.
            wait.countDown();
            System.out.println(new Date() + ": " + Thread.currentThread().getName() + " - Finished");
            // Record that I've finished.
            finished();
        } catch (InterruptedException ex) {
            System.out.println(Thread.currentThread().getName() + " - Interrupted");
        }
    }

}

Runnable cleanup = new Runnable() {

    @Override
    public void run() {
        try {
            // Wait up to 3 seconds for both threads to clear.
            wait.await(3, TimeUnit.SECONDS);
            // Do your cleanup stuff here.
            // ...
            System.out.println(new Date() + ": " + Thread.currentThread().getName() + " - Finished");
        } catch (InterruptedException ex) {
            System.out.println(Thread.currentThread().getName() + " - Interrupted");
        }
    }

};

final AtomicBoolean cleanupStarted = new AtomicBoolean(false);

private void finished() {
    // Make sure I only start the cleanup once.
    if (cleanupStarted.compareAndSet(false, true)) {
        new Thread(cleanup, "Cleanup").start();
    }
}

public void test() throws InterruptedException {
    // ThreadA
    Thread threadA = new Thread(new TestRun(10000), "Thread A");
    // ThreadB
    Thread threadB = new Thread(new TestRun(30000), "Thread B");
    // Fire them up.
    threadA.start();
    threadB.start();
    System.out.println(new Date() + ": Done");
}

【讨论】:

  • 任何一个线程都可以先完成。我想在第二个线程完成后立即清理,但在第一个线程完成后不超过 3 秒。
  • @Gert-Jan - 在这种情况下,而不是 threadA.join() 使用 while (wait.getCount() > 1) {Thread.sleep(1000);} - 不是很漂亮,但它应该可以工作。
  • 谢谢,例如,我将尝试使用CountDownLatch 编写一个实现。使用睡眠不是一个好的选择,因为我确实需要尽快清理,而睡眠会带来额外的延迟。
  • @Gert-Jan - 第二个选项允许先完成任一线程。
  • 太好了,看起来这正是我所需要的,没有 wait() 和 notify()。缺点是更多的代码和一个额外的线程,但我想你不能拥有这一切;)
【解决方案2】:

由于done 方法是同步的,所以一次只能执行一个线程,这一秒将等待发送通知,直到第一个完成其整个工作,这可能会导致性能瓶颈。

我宁愿用短的同步块来设计它,它主要更新boolean first

【讨论】:

  • wait 释放锁,因此第二个线程可以在第一个线程调用 wait 后立即继续
  • 如果我考虑到,那么会有一个竞争条件,如果第二个线程在 3 秒后调用,而且首先为什么第一个线程应该多花 3 秒?
  • 同步方法中只能有一个运行线程,因此不能存在竞争条件。 3 秒是允许第二个线程完成它正在做的事情的宽限期,尽管通常线程应该在彼此之后的几毫秒内完成。
  • 你没明白,我说如果第一个线程完成3秒等待后第二个线程到达,第二个线程将等待直到第一个线程释放对象锁。我宁愿没有宽限期,几乎没有时间就可以完成工作。而且如果这个线程开始做耗时的任务,那么这个宽限期可能是不合适的。
猜你喜欢
  • 2014-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-14
  • 1970-01-01
相关资源
最近更新 更多