How to maintain mutable state in a Java Singleton

2019-07-07 03:20发布

问题:

I have a Singelton in Java (in an OSGi Service) and want to maintain some state in it (a counter).

Should this variable be static? or synchronized? or both?

Or should i wrap the actions in a syncronized method? (would this be any different than just making the var syncronized?)

I want consumers of the service actions to increment this counter.

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

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

Update: How would I so something like a Map or a List? Is it preferred to use Atomic versions of these too? Or is Synchronized better for this?

回答1:

The problem with singletons is that they require a scope. If you register a service in OSGi then this is a singleton in the framework. However, since OSGi avoids statics like the plague people could start multiple frameworks (nested or as siblings) in the same VM and that could mean your service gets registered multiple times in different frameworks. In general, this is exactly what you want. If this not sufficiently singleton, then what should be the scope? The VM, the process, the machine, the network, the world? All the tricks people provide you for creating singletons forget to tell you that they scope only for the class loader you happen to be in.

In OSGi, assume your scope is the framework. So just register a single service and use instance variables. Since OSGi runs in a concurrent environment, you must, as all the other posts indicate, use synchronized methods or better AtomicLong/AtomicInteger.

If you have multiple services that need to share a singleton, just create an extra service to represent the singleton.

Never use statics since they can reduce the reusability of your code significantly, they have all the evils of global variables. One of the beauties of pure OSGi is that it allows you program almost completely with instances and never have to work with statics and class names (which suffer from the same global variable problem).



回答2:

This is a perfect opportunity for using Atomics:

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

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

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

This is lock-free and therefore generally faster and more efficient.

This mechanism will work just as well with singletons or non-singletons.



回答3:

A normal OSGi service is a singleton in that framework if you register it only once. And if you're using Declarative Services that is what will happen by default.

By normal I mean not created by a service factory. Also the framework bit is important, some vendors support multiple frameworks in a single JVM.

Do not use static - this not the OSGi way :-)

Your concern is only concurrent access, so as others have commented, something like AtomicLong would be suitable.

Watch out for overflow - do you expect your counter to reach Long.MAX_VALUE (> 9,223,372,036,854,775,807)? Otherwise you'll eventually see negative counts.