如何在Java Singleton中维护可变状态

3

我在Java中有一个Singleton(在OSGi服务中),想要维护其中的一些状态(一个计数器)。

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

或者我应该将这些操作包装在同步方法中?(这与仅使变量同步有什么不同?)

我希望服务消费者可以增加这个计数器。

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

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

更新:我如何使用Map或List?是否也应该使用它们的原子版本?还是使用Synchronized更好?

1
我不熟悉OSGI,但似乎您需要将其设为静态,并且AtomicInteger会为您提供所需的线程安全性:private AtomicInteger count = new AtomicInteger(0);,在helloWorld中:count.incrementAndGet();。不需要其他同步。 - assylias
@assylias - 把它作为答案发布,我会点赞。这是正确的方法。 - OldCurmudgeon
@OldCurmudgeon在我的手机上... 随意发表类似的回答。 - assylias
静态变量是有害的...OSGi一直试图避免使用静态变量,以便您可以嵌套框架、并行运行多个框架等。静态变量是具有所有不良副作用的全局变量。 - Peter Kriens
3个回答

7
单例模式的问题在于它需要一个“作用域”。如果您在OSGi中注册了一个服务,那么这个服务在框架中就是单例。然而,由于OSGi避免使用静态变量,因此人们可以在同一虚拟机中启动多个框架(嵌套或作为兄弟),这可能意味着您的服务在不同的框架中被注册多次。通常情况下,这正是您想要的。如果这还不够单例,那么作用域应该是什么?虚拟机、进程、机器、网络、全世界?所有为创建单例提供的技巧都忘记告诉您,它们只适用于您所在的类加载器。
在OSGi中,假设您的作用域是框架。因此,只需注册一个单一服务并使用实例变量即可。由于OSGi在并发环境中运行,因此您必须像其他所有帖子所指示的那样使用同步方法,或者更好地使用AtomicLong / AtomicInteger。
如果有多个服务需要共享单例,请创建一个额外的服务来表示单例。
永远不要使用静态变量,因为它们会显著降低代码的可重用性,具有全局变量的所有缺点。纯OSGi的美妙之处在于它允许您几乎完全使用实例进行编程,而无需处理静态变量和类名(这些都会受到同样的全局变量问题的影响)。

+1最佳答案剖析单例的现实——这一切都与“作用域”有关。 - earcam
谢谢。我的理解是:尽可能使用原子操作,否则使用尽可能小的块进行同步。 - empire29
Peter,静态字段会在各种框架下注册的所有服务单例之间共享吗?或者来自所有服务工厂? - empire29
静态字段是有害的,因为它们实际上是全局变量。此外,在多类加载器的世界中,比如OSGi,你永远不知道什么是全局的。 - Peter Kriens

2

这是使用原子操作的绝佳机会:

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

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

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

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

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


在Java中,您不能定义静态类 - 只能定义方法、字段和匿名块(如果我没记错的话)。 - earcam
@earcam - 复制/粘贴问题 - 你可以定义static 内部类。已修复。 - OldCurmudgeon
+1 内部类 当然,我忘了这个 - 但是将服务作为内部类就有点奇怪了 =) - earcam
@earcam - 这很奇怪 - 我通常会在IDE中测试我发布的代码,以确保正确的格式和语法,通常作为Test类的静态内部类。有时当我将其复制到SO中时,我会忘记删除“static”。 - OldCurmudgeon
明白了,我只是在挑剔而已 =) - earcam
这个行业有很多吹毛求疵的人 - 有时这正是我们做好工作的原因。 :) - OldCurmudgeon

2

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

我所说的正常是指不是由服务工厂创建的。 此外,框架部分很重要,一些供应商支持在单个JVM中使用多个框架。

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

您唯一需要关注的是并发访问,因此如其他人所评论的那样,像AtomicLong这样的东西就很合适。

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


@FredOverflow - 你说得很对,我最初使用了AtomicInteger,然后切换到了AtomicLong,现在已经修复了,谢谢 =) - earcam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接