【问题标题】:How to maintain mutable state in a Java Singleton如何在 Java Singleton 中维护可变状态
【发布时间】:2026-01-28 23:15:01
【问题描述】:

我有一个 Java 中的 Singelton(在 OSGi 服务中),并希望在其中维护一些状态(计数器)。

这个变量应该是静态的吗?或同步?还是两者都有?

或者我应该将动作包装在同步方法中吗? (这与仅使 var 同步有什么不同吗?)

我希望服务操作的消费者增加这个计数器。

public MyServiceImpl implements MyService {
    private int count = 0; // static? syncronized?

    public String helloWorld() { count++; return "Hello World"; }
    public int getHelloCount() { return count; }
}

更新:我怎么会像地图或列表这样的东西?是否也更喜欢使用这些的原子版本?或者同步更好?

【问题讨论】:

  • 我对 OSGI 不熟悉,但您似乎需要将其设为静态,并且 AtomicInteger 将为您提供所需的线程安全:private AtomicInteger count = new AtomicInteger(0);helloWorld:@ 987654325@。无需其他同步。
  • @assylias - 作为答案发布,我会投票。这是正确的做法。
  • @OldCurmudgeon 在我的手机上...随时发布类似的答案。
  • 静态是邪恶的...... OSGi 已经尝试不惜一切代价避免静态,以便您可以嵌套框架,并排运行多个框架等。静态是一个全局变量,它有它所有不好的一面效果。

标签: java synchronization singleton osgi


【解决方案1】:

单例的问题在于它们需要一个作用域。如果您在 OSGi 中注册服务,那么这是框架中的单例。然而,由于 OSGi 避免了像瘟疫这样的静态问题,人们可以在同一个 VM 中启动多个框架(嵌套或作为兄弟),这可能意味着您的服务在不同的框架中被多次注册。一般来说,这正是您想要的。如果这还不够单例,那么范围应该是什么?虚拟机、进程、机器、网络、世界?人们为您提供的创建单例的所有技巧都忘记告诉您它们仅适用于您碰巧所在的类加载器。

在 OSGi 中,假设您的范围是框架。所以只需注册一个服务并使用实例变量。由于 OSGi 在并发环境中运行,因此正如所有其他帖子所指出的那样,您必须使用同步方法或更好的 AtomicLong/AtomicInteger。

如果您有多个服务需要共享一个单例,只需创建一个额外的服务来表示该单例。

永远不要使用静态变量,因为它们会显着降低代码的可重用性,它们具有全局变量的所有弊端。纯 OSGi 的优点之一是它允许您几乎完全使用实例进行编程,而不必使用静态和类名(它们遭受相同的全局变量问题)。

【讨论】:

  • +1 剖析单例现实的最佳答案 - 这完全是关于 scope
  • 谢谢;我的收获是:尽可能使用原子,否则使用尽可能小的块进行同步。
  • Peter,静态字段会在各种框架下注册的所有服务单例之间共享吗?还是来自所有服务工厂?
  • 静态字段是邪恶的,因为它们实际上是全局变量。最重要的是,在像 OSGi 这样的多类加载器世界中,你永远不知道全局是什么。
【解决方案2】:

这是使用 Atomics 的绝佳机会:

public class MyServiceImpl {
  private AtomicInteger helloCount = new AtomicInteger(0);

  public String helloWorld() {
    helloCount.incrementAndGet();
    return "Hello World";
  }

  public int getHelloCount() {
    return helloCount.get();
  }
}

这是无锁的,因此通常更快、更高效。

这种机制同样适用于单例或非单例。

【讨论】:

  • 您不能在 Java 中定义静态类 - 只有方法、字段和匿名块 IIRC
  • @earcam - 复制/粘贴问题 - 你可以定义static inner类。 FTY
  • +1 inner class 是的,当然,这就是我忘记的 - 但是作为内部类的服务很奇怪 =)
  • @earcam - 这很奇怪 - 我通常测试我在 IDE 中发布的代码只是为了确保正确的格式和语法,通常作为测试类的静态内部类。有时当我将其复制到 SO 时,我忘记删除 static
  • 明白了,我只是个书呆子=)
【解决方案3】:

如果您只注册一次普通的 OSGi 服务,那么它就是该框架中的单例。如果您使用的是声明式服务,则默认情况下会发生这种情况。

正常我的意思是不是由服务工厂创建的。框架位也很重要,一些供应商在单个 JVM 中支持多个框架。

不要使用静态 - 这不是 OSGi 方式 :-)

您关心的只是并发访问,因此正如其他人所评论的那样,AtomicLong 之类的内容是合适的。

注意溢出 - 您是否希望您的计数器达到 Long.MAX_VALUE (> 9,223,372,036,854,775,807)?否则你最终会看到负数。

【讨论】:

  • @FredOverflow - 你说的很对,我最初回答是使用 AtomicInteger,然后切换到 AtomicLong,现在已修复,谢谢 =)
最近更新 更多