简体   繁体   中英

ThreadLocal with Singletons

I am working on the following piece of code. Two threads requiring their own instance of a singleton. Thread Local is an obvious solution to this. However I am still facing issues running the threads with their own local copy. I have an example of the scenario in a couple of java classes.

public class Singleton1 {

private int i = 0;

private static Singleton1 instance;

private Singleton1() {
}

public static final Singleton1 getInstance() {
    if (instance == null) {
        instance = new Singleton1();
    }
    return instance;
}

public int increment() {
    return i++;
}

}

public class Holder1 {

private final Singleton1 instance;

public Holder1() {
    ThreadLocalSingleton1 singleton1 = new ThreadLocalSingleton1();
    instance = singleton1.get();
}

public int increment() {
    return instance.increment();
}

private class ThreadLocalSingleton1 extends ThreadLocal<Singleton1> {

    @Override
    protected Singleton1 initialValue() {
        return Singleton1.getInstance();
    }

}

}

public class HolderTest {

/**
 * @param args
 */
public static void main(String[] args) {
    HolderTest test = new HolderTest();
    HolderThread thread1 = test.getHolderThread("thread1");
    HolderThread thread2 = test.getHolderThread("thread2");
    thread1.run();
    thread2.run();

}

public HolderThread getHolderThread(String name) {
    return new HolderThread(name);
}

private class HolderThread implements Runnable {
    String name;

    Holder1 holder1 = new Holder1();

    public HolderThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        while (true) {
            System.out.println(name + " " + holder1.increment());
        }
    }
}

When the ThreadLocal wrappers call getInstance on the Singleton classes I do not get a new instance each time? How do I make this work for my purposes?

The code above is a simple version of the actual code I am working with. I have Singleton classes which I cannot change from being singletons. I am creating a test client which needs to run as a single process but with many threads. Each of these threads needs to have its own instance of these singletons.

You seem to be painted into a corner.

On the one hand, you have an existing codebase that you need to test and that code uses (genuine, properly implemented) singleton objects. In particular, the declaration of the Singleton1() constructor as private in your examplar class Singleton1 makes it impossible to declare a subclass.

On the other hand, your testing requires you to write a client with lots of these Singleton1 instances.

On the face of it, that is impossible. There is no way to make two instances of the Singleton1 class in the JVM, and there is no way to declare a (compilable / loadable) subclass of Singleton1 .

This is per design ; ie it is what the designer of the Singleton1 class intended. (And if not, then the answer is to change Singleton1 to make it easier to test. For example, by making the Singleton1 constructor not private so that multiple instances can be created for test purposes. )

(For instance, your current attempt at implementing ThreadLocalSingleton1 fails because the Singleton1.getInstance() returns the global instance of Singleton1 . No matter what you do, there is no way to create any other instance of the Singleton1 class.)

However, I can think of two workarounds for your particular use-case.

I am writing a test client which needs to run as as single java process. The test client is used for load testing will have X threads accessing a server using a core project (that I cannot change too much) which has many singletons. The singletons hold state which will be required per thread.

Here are the workarounds:

  1. Instead of running one JVM with N instances of your test thread, run N separate JVMs each with a single test thread. Each JVM / test thread can have its own instance of Singleton .

  2. Have each of your test threads create a new classloader, and use that classloader to dynamic load the Singleton1 class and everything with a direct or indirect static dependency on the Singleton1 type. The idea is for each classloader to load its own copy of the Singleton1 class. Since each copy will be a distinct type 1 , it will have its own private static Singleton1 instance variable.

Note that these workarounds do provide not "thread-local" instances of your Singleton1 class. That is both technically impossible ... and a contradiction of the definition of singleton.

In both cases you have true singleton instances, but they are instances of different Singleton1 types ... for different reasons.


1 - At runtime, the type of a class instance is conceptually a pair consisting of the fully qualified name of the class and the identity of the classloader that loaded the class. If the same bytecode file is loaded by different classloaders, then you get different runtime types.

Your target class shall not be singleton, but you must access it just using the ThreadLocal, and creating a new instance if ThreadLocal instance is empty (doesn't hold a reference to an instance of your target object).

Another solution is to make your Target class singleton, and hold its state in ThreadLocal variables.

Please, take a look at the ThreadLocal working example below:

public class YourDataHolder {
    private static ThreadLocal dataVariable = new ThreadLocal();
    private static YourDataHolder dataHolderVar;
    private YourDataHolder() { }
    public void storeDataToThreadLocal (String userName) {
        dataVariable.set(userName);
    }
    public String readDataFromThreadLocal ()  {
        if (dataVariable.get() != null) {
            return (String) dataVariable.get();
        }
    }
    public static ServiceVersionHolder getInstance () {
        if (dataHolderVar == null) {
         dataHolderVar = new YourDataHolder();
        }
        return dataHolderVar;
    }
}

Do you mean something like this?

private static final ThreadLocal<AtomicInteger> COUNTER = new ThreadLocal<AtomicInteger>() {
    @Override
    protected AtomicInteger initialValue() {
        return new AtomicInteger();
    }
};

public static int incrementAndGet() {
    return COUNTER.get().incrementAndGet();
}

Use synchronized for multithreading.

public static synchronized final Singleton getInstance() {

This way the threads will "lock" the method: only one thread will be allowed to enter the method at a time, other threads will block until the method is unlocked (the thread executing it leaves). You won't have those concurrency issues.

Also you don't need 2 singletons (which IMHO actually makes no sense and defeats the very own purpose of a singleton...).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM