Junit test that fails if singleton getInstance () method is out of sync

I have this singelton database that I created and this Junit test that I created:

one-point

package SingeltonDBVersion1;

import GlobalSetting.User;

/****************************************************************************
 * This is the SingeltonDB. it warps the object DBconn according to the
 * Singleton pattern. it receive name and password (i.e. DBConn parameters) and
 * if it is the first time that a UserContorll try to get an instance it connect
 * to the database. After that, the DBConn instance will be return to the user.
 *****************************************************************************/
public class SingeltonDB {
    private static DBconnImpl db = null;
    private static SingeltonDB singalDb = null;
    boolean first = true;

    private SingeltonDB(String username, String password) {
        if (first) {
            try {
                System.out.println("first");
                Thread.sleep(5000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            first = false;
        }
        db = new DBconnImpl();
    }

    public static SingeltonDB getInstance(String username, String password)
            throws Exception {
        if (db != null) {
            return singalDb;
        }

        singalDb = new SingeltonDB(username, password);
        System.out.println("The database is now open");
        db.connect(username, password);
        System.out.println("The database was connected");
        return singalDb;
    }

    public void create(String tableName) throws Exception {
        db.create(tableName);
    }

    public User query(String tableName, int userID) throws Exception {
        if (db == null) {
            System.out.println("Error: the database is not open");
            return null;
        }
        return (db.query(tableName, userID));
    }

    public void update(String tableName, User user) throws Exception {
        if (db == null) {
            System.out.println("Error: the database is not open");
            return;
        }
        db.update(tableName, user);
    }

    public void disconnect() throws Exception {
        db.disconnect();
    }

    public static void reset() throws Exception {
        db = null;
    }
}

      

Junit

package Tests;

import java.util.concurrent.CountDownLatch;

import org.junit.Test;

import SingeltonDBVersion1.SingeltonDB;


public class SingeltonDBVersion1Tests {

        @Test
        public synchronized void testSynch() throws Exception {
            int num=2;
            CountDownLatch doneSignal = new CountDownLatch(num);

            MySingeltonDB[] instances = new MySingeltonDB[num];
            for (int i = 0; i < num; i++) {
                instances[i]=new MySingeltonDB(doneSignal);

            }
            SingeltonDB.reset();
            for (int i = 0; i < num; i++) {
                instances[i].run();

            }
             doneSignal.await(); 
                for (int i = 0; i < num; i++) { 
                    for (int j = i; j < instances.length; j++) {
                        if (instances[i].getDB()!=instances[j].getDB())
                        {
                            throw (new Exception());
                        }
                    }
                }
        }


    class MySingeltonDB implements Runnable {
        SingeltonDB db;
        CountDownLatch doneSignal;

        public MySingeltonDB(CountDownLatch doneSignal) {
            this.db = null;
            this.doneSignal = doneSignal;
        }

        public SingeltonDB getDB() {
            return db;
        }

        @Override
        public void run() {
            try {
                this.db = SingeltonDB.getInstance("MyAccount", "123");
                doneSignal.countDown();
                System.out.println("----------------------------------------->"+db );
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }
}

      

What I'm trying to do here is create 2 instances of a singleton if and only if the singletonDB: getInstance () method is out of sync. but for some reason the singleton works correctly and returns one instance of the class even if I am not using a synchronized method.

+3


source to share


2 answers


try my test, it reproduces the problem:



static class Singleton {
    static Singleton i;

    static Singleton getInstance() {
        if (i == null) {
            i = new Singleton();
        }
        return i;
    }
}

public static void main(String[] args) throws Exception {
    Callable<Singleton> c = new Callable<Singleton>() {
        @Override
        public Singleton call() throws Exception {
            return Singleton.getInstance();
        }
    };
    ExecutorService ex = Executors.newFixedThreadPool(2);
    for(;;) {
        Future<Singleton> f1 = ex.submit(c);
        Future<Singleton> f2 = ex.submit(c);
        if (f1.get() != f2.get()) {
            System.out.println("!!!");
        }
        Singleton.i = null;
    }
}

      

+5


source


Your test is incomplete. Because you are waiting for all threads to complete, but you do not expect all threads to be ready. Add another new one new CountDownLatch(1)

before starting the flow, for example in this example. This ensures that all threads start at the same time.

import org.junit.Test;

import SingeltonDBVersion1.SingeltonDB;


public class SingeltonDBVersion1Tests {

        @Test
        public synchronized void testSynch() throws Exception {
            int num=2;
            CountDownLatch startSignal = new CountDownLatch(1);
            CountDownLatch doneSignal = new CountDownLatch(num);

            SingeltonDB.reset();    
            Thread[] instances = new Thread[num];
            for (int i = 0; i < num; i++) {
                instances[i]=new Thread(new MySingeltonDB(startSignal, doneSignal)).start();

            }
             startSignal.countDown(); 
             doneSignal.await(); 
                for (int i = 0; i < num; i++) { 
                    for (int j = i; j < instances.length; j++) {
                        if (instances[i].getDB()!=instances[j].getDB())
                        {
                            throw (new Exception());
                        }
                    }
                }
        }


    class MySingeltonDB implements Runnable {
        SingeltonDB db;
        CountDownLatch startSignal;
        CountDownLatch doneSignal;

        public MySingeltonDB(CountDownLatch startSignal, CountDownLatch doneSignal) {
            this.db = null;
            this.startSignal = startSignal;
            this.doneSignal = doneSignal;
        }

        public SingeltonDB getDB() {
            return db;
        }

        @Override
        public void run() {
            try {
                startSignal.await();
                this.db = SingeltonDB.getInstance("MyAccount", "123");
                doneSignal.countDown();
                System.out.println("----------------------------------------->"+db );
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }
}

      



And start using threads in general.;) The code from your example runs in one thread. I would be surprised if it didn't work out.

+1


source







All Articles