【问题标题】:Multiple Threads calling static helper method多个线程调用静态辅助方法
【发布时间】:2011-09-06 08:56:37
【问题描述】:

我有一个在 Tomcat 上运行的 Web 应用程序。

需要在 Web 应用程序的多个位置进行多项计算。我可以进行这些计算静态辅助函数吗?如果服务器有足够的处理器内核,那么对该静态函数的多次调用(由对不同 servlet 的多个请求产生)可以并行运行吗?还是一个请求必须等到另一个请求完成调用?

public class Helper {
    public static void doSomething(int arg1, int arg2) {
        // do something with the args
        return val;
    }
}

如果调用并行运行: 我有另一个带有静态函数的助手类,但是这个类包含一个用于静态函数的私有静态成员。如何确保函数是线程安全的?

public class Helper {

    private static SomeObject obj;

    public static void changeMember() {
        Helper.obj.changeValue();
    }

    public static String readMember() {
        Helper.obj.readValue();
    }

}

changeValue()readValue() 读取/更改Helper.obj 的相同成员变量。我必须同步整个静态函数,还是只同步使用Helper.obj 的块?如果我应该使用块,我应该使用什么对象来锁定它?

【问题讨论】:

  • 谢谢大家,你们肯定回答了我的问题,也让我深思。不幸的是,我不能接受多个答案:-/

标签: java multithreading static static-methods


【解决方案1】:

我可以将这些计算设为静态辅助函数吗?如果服务器有足够多的处理器内核,那么对该静态函数的多次调用(由对不同 servlet 的多个请求产生)可以并行运行吗?

是的,是的。

我是否必须使整个静态函数同步

这会起作用的。

或者只是使用Helper.obj的块

这也行。

如果我应该使用块,我应该使用什么对象来锁定它?

使用static Object

public class Helper {

    private static SomeObject obj;
    private static final Object mutex = new Object();

    public static void changeMember() {
        synchronized (mutex) {
            obj.changeValue();
        }
    }

    public static String readMember() {
        synchronized (mutex) {
            obj.readValue();
        }
    }
}

不过,理想情况下,您应该将帮助程序类编写为不可变的(无状态或其他),这样您就不必担心线程安全。

【讨论】:

  • 在负载未知的 Web 应用程序的上下文中,在静态锁上同步可能会导致服务器性能问题 - 取决于负载和同步代码所花费的时间。我见过这样的“解决方案”导致整个应用程序服务器无法使用:有大量请求等待服务等待这个瓶颈。
  • 有些对象需要共享,例如会话管理。什么是一个优雅的解决方案?
  • @Mario 你能澄清你的意思吗? “会话管理”不是对象。
  • @Tomasz Stanczak 我同意了解并仔细查看此类代码,但在必要时不同步它不是一种选择。或者你会推荐什么?
  • 你应该让容器处理会话管理!你不使用HttpServletRequest#getSession()吗?
【解决方案2】:

您应该在一个类中捕获计算,并为每个线程创建一个类的实例。如您所知,您现在拥有的不是线程安全的,并且要使其成为线程安全的,您必须在静态资源/访问该静态资源的方法上进行同步,这将导致阻塞。

请注意,有一些模式可以帮助您解决此问题。您可以使用策略模式(以其规范形式,必须在运行时选择策略,这可能适用也可能不适用)或变体。只需使用执行方法(以及具有该方法的接口)为每个计算创建一个类,并传递一个上下文对象来执行。上下文包含计算的所有状态。每个线程一个策略实例及其上下文,您应该不会有任何问题。

【讨论】:

  • 第一个用例不需要是线程安全的,因为只有原始参数并且不涉及类成员。还是我错过了什么?
  • @marioP,没错,但总的来说,这可以通过实例轻松完成,所以我的答案是完全摆脱静态方法。恕我直言,如果一半的计算是在静态方法中,而另一半是在实例中...
  • @Mario 如果没有状态存储在类字段(static 或非静态)中,那么您就可以了。
  • @hvgotcodes,如果我创建一个普通的可实例化类或使用策略模式进行这些计算,这对应用程序的内存使用有何影响?开销可以忽略不计吗?
  • @marioP,类的实例总是有成本的——成本相对较小。如果每个计算都带有大量数据,那么无论您是否使用静态方法,这都是正确的。您计划进行多少次计算?
【解决方案3】:

如果您不必共享它,您可以将其设为线程本地,那么它不必是线程安全的。

public class Helper {

private static final ThreadLocal<SomeObject> obj = new ThreadLocal<SomeObject>() {
    public SomeObject initialValue() {
        return enw SomeObject();
    }
}

public static void changeMember() {
    Helper.obj.get().changeValue();
}

public static String readMember() {
    Helper.obj.get().readValue();
}

}

【讨论】:

  • 不幸的是,它必须共享,但我会看看 ThreadLocal 类。也许它在其他情况下证明是有用的。
【解决方案4】:

我将在这里总结一下 cmets 中对 Matt Ball 的回答所说的话,因为它最后变得很长并且消息丢失了:消息是

在像网络/应用程序服务器这样的共享环境中,您应该非常努力地在不同步的情况下找到解决方案。使用在静态对象上同步的静态帮助器对于屏幕前有单个用户的独立应用程序可能工作得很好,在多用户/多应用程序场景中这样做很可能会以非常差的性能结束 - 这实际上意味着序列化访问在您的应用程序中,所有用户都必须等待同一个锁。您可能很长时间都没有注意到问题:如果计算速度足够快并且负载分布均匀。

但是突然之间,您的所有用户可能会在上午 9 点尝试进行计算,而您的应用程序将停止工作!我的意思是不是真的停下来,但他们都会在锁上阻塞并排成一个巨大的队列。

现在不管共享状态的必要性,既然您最初将计算命名为同步的主题:它们的结果是否需要共享?还是这些计算特定于用户/会话?在后一种情况下,根据 Peter Lawrey 的 ThreadLocal 就足够了。否则我会说为了整体性能,最好为每个需要它们的人复制计算,以免同步(取决于成本)。

会话管理也应该更好地留给容器:它已经过优化,可以有效地处理它们,如有必要,包括集群等。我怀疑一个人可以在不投入大量工作并在途中产生大量错误的情况下做出更好的解决方案.但正如马特鲍尔所说,最好单独询问。

【讨论】:

    【解决方案5】:

    在第一种情况下,您不必担心线程问题,因为变量对于每个线程都是本地的。但是,您在第二种情况下正确识别了问题,因为多个线程将读取/写入同一个对象。同步方法将起作用,同步块也是如此。

    【讨论】:

      【解决方案6】:

      对于第一部分: 是的,这些调用是独立的,并且在被不同线程调用时并行运行。

      最后一部分: 在并发对象、虚拟对象或类对象上使用同步块。注意级联同步块。当它们以不同的顺序获取时,它们可能会导致死锁。

      【讨论】:

        【解决方案7】:

        如果您担心同步和线程安全,请不要使用静态助手。使用您的辅助方法创建一个普通类,并根据 servlet 请求创建一个实例。保持简单:-)

        【讨论】:

        • 我不同意。拥有良好的静态实用程序方法非常普遍,而且是一件好事。它减少了复制和粘贴代码,并保持“干净”和简单,因为不太复杂。
        猜你喜欢
        • 2012-05-25
        • 2017-06-22
        • 2014-10-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-09-14
        • 2017-10-22
        相关资源
        最近更新 更多