【问题标题】:Is Sun's Thread.join method broken because it synchronizes usng the Thread object?Sun 的 Thread.join 方法是否因为使用 Thread 对象同步而损坏?
【发布时间】:2010-10-23 05:25:09
【问题描述】:

通过运行测试程序和查看源代码,可以清楚地看出,Sun 实现的该方法并不是简单地将时间分配给指定线程,而是实际上首先尝试获取线程对象的监视器。具体来说,该方法实现为“同步”。

注意wait和notify方法也需要monitor,但和join不同的是,调用前获取monitor是调用者的责任,文档中明确说明了这一点。 Javadocs 中没有记录 join 依赖于监视器的事实,尽管进行推断可能是很自然的。

文档是否足够清晰?

另外,如果线程由于某种原因无法获得监视器,它会挂起,可能永远挂起。在等待监视器时,线程是不可中断的,并且不会按照文档中的描述抛出 InterruptedException。另一方面,不清楚为什么一个线程不能获得监视器,除非是在编程错误的情况下。

担心监视器争用是否合理?

最后,让超时的操作依赖于获取监视器似乎是不合适的,除非可以保证获取监视器的任务本身会超时。

是否依赖于 join() 的监视器是一个合理的实现?是否有可能以任何其他方式实现它?

【问题讨论】:

  • 标题中有问题。我不认为这是不合理的。这是错误的,但不是一个不合理的问题。

标签: java multithreading join timeout


【解决方案1】:

Thread.join 调用wait,这会释放监视器。由于这意味着“加入”线程也不会阻止任何其他线程调用 join,我怀疑这回答了您的大多数其他查询。它不会阻止另一个调用者在线程的监视器上同步(哦,公共监视器的乐趣),但这意味着常见情况可以正常工作。

只是为了证明您的第一点是错误的,这里有一个示例,它创建了 10 个线程,每个线程在主线程上等待 5 秒。 (请忽略对Date的可怕的异常吞噬和滥用。它仅用于研究线程行为。)

import java.util.*;

public class Test
{
    public static void main(String[] args) throws Exception
    {
        for (int i=0; i < 10; i++)
        {
            new Thread(new ThreadJoiner(Thread.currentThread(), i))
                     .start();
        }
        try
        {
            Thread.sleep(10000);
        }
        catch (InterruptedException e) {}
    }

    private static class ThreadJoiner implements Runnable
    {
        private final Thread threadToJoin;
        int id;

        public ThreadJoiner(Thread threadToJoin, int id)
        {
            this.threadToJoin = threadToJoin;
            this.id = id;
        }

        public void run()
        {
            try
            {
                System.out.println("Thread " + id +
                                   " waiting at " + new Date());
                threadToJoin.join(5000);
                System.out.println("Thread " + id +
                                   " finished waiting at " + new Date());
            }
            catch (InterruptedException e) {}
        }
    }
}

如果你运行它,你会看到所有线程几乎同时开始和结束它们的等待。如果您的担忧是有根据的,您就不会像您那样“交错”结束。

【讨论】:

  • 感谢您花时间编写一些代码。你是对的,原来的大部分问题都是错误的。我开始考虑一个问题,即线程持有监视器的问题,而不是简单地等待、加入或通知,并且衍生出一些没有多大意义的示例。关键是要询问实现是否符合该方法将超时的保证。它似乎取决于它不应该做的事情,比如其他线程正在做什么。
【解决方案2】:

很明显,Sun 实现的方法并不简单地让时间给指定线程,实际上它首先尝试获取线程对象上的监视器。

它不会屈服于加入的线程,它只是在假设线程将在某个时刻运行到完成的情况下等待。在线程上使用 join() 不会使其比任何其他准备运行的线程更有可能运行。

  1. 如果 N 个线程都尝试加入同一个线程,并且它们都指定了相同的超时 T,那么其中一个线程最终将等待至少 N*T 毫秒。换句话说,每个线程都必须“等待轮到它”来执行它的等待。单独的线程串行而不是并行执行连接是否合理?

线程被设计为同时工作。如果他们都在等待,他们会同时这样做。一个等待的线程不会让另一个线程等待更长的时间。

.2。进入具有非零超时的连接的线程可能永远不会返回。

除非您打算这样做,否则不会。

这是因为无法保证监视器永远可用。

您建议的情况只有在线程获得线程上的锁然后永远持有它而无需等待时才会发生。这是一个编程错误。恕我直言,您永远不应该直接获得线程对象的锁。

如果线程在阻塞 I/O 操作之前获得了自己的监视器,并且该操作挂起,那么任何尝试加入该线程的线程也将挂起。

Java 不能保护您免受自己 JVM 中的恶意代码的侵害。

明确超时的操作无限期挂起是否合理?

如果它被无限期锁定,是的。

.3。为了编写使用该方法的正确程序,调用者必须事先知道目标线程或其他线程是否可以持有监视器。

永远不要锁定线程对象,你没有理由需要这样做,你也不会有这个问题。如果您想开始研究可能使其他开发人员或您自己感到困惑的所有方式,那么 Threads 不是您开始恕我直言的地方。

例如,假设线程 1 正在执行某种处理工作,然后线程 2 以 0 的超时时间加入线程 1,然后线程 3 尝试以 10 毫秒的超时时间加入线程 1,会发生什么情况。 0 超时连接意味着线程 2 将一直等到线程 1 退出。但是线程 3 不能开始等待,直到线程 2 释放监视器,

线程 2 在调用 wait 后立即释放监视器。它不能同时等待和保持显示器。

因此,线程 3 的 10 毫秒等待有效地被静默转换为无限期等待,这不是调用者的意图。

没有。查看以前的 cmets。

不要求调用者知道实现细节是否违反了封装原则?

会的。

.4。如果线程因为无法获取监视器而被阻塞,则它是不可中断的,并且不会按照文档中的描述抛出 InterruptedException。因此,一个线程不仅可能等待比预期更长的时间,甚至无限期地等待,它可能变得完全没有响应,导致整个程序挂起。可中断操作变得无响应是否合理?

是的。但这是一种非常罕见的情况。如果你使用写的代码,你所指的情况最多只会存在几毫秒。

总的来说,让超时的操作依赖于获取监视器似乎是不合适的,除非可以保证获取监视器的任务本身会超时。线程连接坏了吗?

您可以使用更新的 Java 5 并发库执行您的建议。

但是,我建议您不要假设超时可以保证精确到毫秒。此方法使用的 currentTimeMillis() 在 Windows XP 上仅精确到大约 16 毫秒,等待/睡眠通常比在 Linux 上的小超时时间长 2 毫秒。

恕我直言,如果您需要超过 40 毫秒的精度,您可能会遇到困难,但是如果您解决这个问题,您会发现这不是问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-07-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-01
    • 1970-01-01
    • 2019-01-22
    相关资源
    最近更新 更多