【问题标题】:Junit test that will fail if singleton getInstance() method is not synchronized如果单例 getInstance() 方法不同步,Junit 测试将失败
【发布时间】:2014-12-17 11:15:19
【问题描述】:

我有这个我已经构建的 singelton 数据库,以及我创建的这个 Junit 测试:

单身

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;
    }
}

朱尼特

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();
            }
        }

    }
}

我在这里要做的是创建 2 个单例实例,当且仅当 singletonDB 方法:getInstance() 不同步。但由于某种原因,即使我没有在方法中使用同步,单例也可以正常工作并返回该类的一个实例。

【问题讨论】:

  • 您似乎是按顺序创建实例,因此您的伪单例很可能在这种情况下实际上作为单例工作。
  • 我会尝试使用 Executor 并创建更多实例 - 2 太有限了。虽然最后,如果您使用真正的单例习语,使用单元测试来检查单例是否是实际的单例将毫无意义(即您可以省去单元测试)。
  • 我是按顺序创建实例,但是每个实例的getInstnace调用都是在线程run()方法中完成的,所以不符合并行吗?
  • 我知道,但这是我在求职面试中被问到的一个问题,看我是否足够了解线程......
  • 是否要求“编写一个如果给定单例类的 getInstance() 方法不同步将失败的 JUnit 测试”?

标签: java multithreading junit synchronization thread-safety


【解决方案1】:

试试我的测试,它重现了问题:

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;
    }
}

【讨论】:

    【解决方案2】:

    您的测试尚未完成。因为您等待所有线程完成,但您没有等待所有线程准备好。在启动线程之前再添加一个新的new CountDownLatch(1),例如this 示例。这将保证所有线程同时启动。

    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();
                }
            }
    
        }
    }
    

    并开始使用线程。 ;) 您示例中的代码在单个线程中运行。如果它失败了,我会感到惊讶。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-29
      • 1970-01-01
      • 1970-01-01
      • 2013-12-01
      • 2011-07-26
      • 1970-01-01
      相关资源
      最近更新 更多