开发者

How to Stop running singleton class multiple times in play framework

开发者 https://www.devze.com 2023-04-13 06:28 出处:网络
I have a singleton class in my play app. This singleton class is a long process which will generate reports from DB which consumes huge amount of memory. When i run my application in dev mode this sin

I have a singleton class in my play app. This singleton class is a long process which will generate reports from DB which consumes huge amount of memory. When i run my application in dev mode this singleton functionality is executing several times. I want this functionality to run only once. What should 开发者_如何转开发I do for that?

My code is:

public class DataGridManagerImpl extends ComponentContainer implements DataGridManager {

private static DataGridManager instance = null;

    private DataGridManagerImpl(){
        load();
    }}

@Override
    public void load() {
//Myreports function
}

public static DataGridManager getInstance(){

          if (instance == null){
             instance = new DataGridServiceManagerImpl();
          }

        return instance;
    }
}

In my controller file inside a template function

DataGridManager dataGridMgr = DataGridManagerImpl.getInstance();

If i access the page it is executing the load reports function again.


Without code explaining how did you create your class it's hard to answer. From what I understand what you want is to run a process only once.

Problably the best approach is to use a Scheduled Job. This will trigger the process at a certain time, and Play ensures that only 1 instance of this process is running at the same time, even if the schedule would indicate another instance has to run. Let's say you have a process scheduled every hour and the process takes 3 hours. The initial process will be the only one running for 3 hours until it finishes.

Now, I would assume you want your process to be recurring as it generate reports. If not, if you only want to run it once, then you may want to use an asynchronous bootstrap job instead. This would run just once, at the beginning of the application.

EDIT on update: during development the @OnApplicationStart may execute several times, as Play may automatically reload the application when you do certain code changes. This is part of the dev process (the same that an @OnApplicationStart job won't start in Dev until the server gets a request).

As it's a job that you only want to run once, you may try to skip it in dev mode using the check:

if(Play.mode == Play.Mode.DEV)

If you need to run it at least once, add a dev-only url that you can access during dev to start the process.

Now, on your update you also mention that you are calling that code in a controller, and that every time the controller is acessed the method is called. That's expected. Singleton doesn't mean that it will run only once, but that there is only 1 object in the system. If in your controller you launch the calculation, that will happen everytime you access the controller.

SECOND EDIT (on comments): Arasu, the other issue is that you are calling the method load() when you construct the object. A singleton doesn't garantee that the object will only be constructed once. It garantees that, once constructed, only 1 object will exist. But it may happen that the object is removed by GC, in this case as per your code if you construct it again then you'll call load() and redo the processing.

The best solution is to not call "load" on constructor, but to force the user (you) to call it after retrieving the instance. An alternative is to set some flag at the beginning of load that detects if the code has been run. Be aware that Play is stateless, so that flag will need to be stored in the database.


the defition of a singleton is that it can run only once, it's practically the nature of the pattern. If you somehow manage to run it multiple times, you might have implementation errors in your singleton.

Recheck the singleton pattern in Wikipedia.

Edit:

This code makes it impossible to fetch more than one instance. How would you get more than one?

public class Singleton {
    private static Singleton _instance;

    private Singleton() {  }

    public static synchronized Singleton getInstance() {
            if (null == _instance) {
                    _instance = new Singleton();
            }
            return _instance;
    }
}

Or do you mean that you instanciate the Singleton class, instead of calling Singleton.getInstance()?


It is possible to have a Singleton doing a time consuming processing and be called the same time by two different threads. I think this is the situation here. The same Singleton object's method is called multiple times from the program.

I have run a little test... two thread calling the same Singleton object and here is the result

Thread[Thread 1,5,main] internal loop number = 0 Object = example.Singeton@164f1d0d
Thread[Thread 2,5,main] internal loop number = 0 Object = example.Singeton@164f1d0d
Thread[Thread 1,5,main] internal loop number = 1 Object = example.Singeton@164f1d0d

and here is the code.

package example;

public class Singeton {

private static final Singeton INSTANCE = new Singeton(); 
private Singeton() {}

public static Singeton getInstance(){
    return INSTANCE;
}

public boolean doTimeConsumingThing(){
    for (int i=0; i<10000000;i++){
        System.out.println(Thread.currentThread() + " internal loop number = " + i +  " Object = "  + toString());
    }
    return true;
}
}

 package example;

public class MulThread extends Thread{
public MulThread(String name) {
    super(name);
}
@Override
public void run() {
    while(true){
        Singeton s = Singeton.getInstance();
        System.out.println("Thread " + getId());
        s.doTimeConsumingThing();
    }
}
public static void main(String[] args) {
    MulThread m1 = new MulThread("Thread 1");
    MulThread m2 = new MulThread("Thread 2");
    m1.start();
    m2.start();
}
}

Please correct my notion above if i am wrong.

Hence what you need is a variable to keep track of the state of the time consuming procedure (i.e. a boolean isRunning) or the times the procedure has been called.

You can also make the pertinent time consuming method of the Singleton synchronized so only one thread can access the method while it is running (in my example if you make the doTimeConsumingThing() synchronized, the second thread will block until the singleton's method called from the first thread is finished.

Hope it helps


I had the same problem in DEV mode, and what I did is create a module for the tasks I don't want to be run at every @OnApplicationStart.

The trick is to launch those tasks in a overriden "onLoad()" method, in the module:

public void onLoad() 
{
    // tasks to run one time only
}

The onLoad() method is called one time only, not each time the application is restarted.


I don't know if this will help, but here are some things to check:

The code in your question is not thread-safe. You're missing the synchronized keyword in getInstance. That could cause the constructor to be called more than once by different threads.

Could DataGridManagerImpl be getting loaded by different classloaders? That static instance variable isn't static for the whole JVM, just static for that class' classloader.

load is public. Could some other code being calling that method?

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号