简体   繁体   中英

How to synchronize two methods in a singleton pattern

I have created a database object according to the singleton pattern. The database object contains 2 methods: connect() and update() .

The update should run multithreaded, meaning that I cannot put synchronized in the update method signature (I want users to access it simultaneously not one at a time).

My problem is that I want to make sure that 2 scenarios according to this flows:

  1. A thread 1 (user1) is the first to create an instance of the DB and thread 2 (user2) is calling the connect() and update() method to this DB - should not give NullPointerException even if by the time that user2 is doing the update() the connect from user1 is not done.

  2. update() should not include synchronized (because of the reason I mentioned above). Thanks for all the helpers!

SingeltonDB

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

    public class SingeltonDB {
        private static DBconnImpl db = null;
        private static SingeltonDB singalDb = null;
          Lock dbLock;

        private SingeltonDB(String username, String password) {
            db = new DBconnImpl();
        }

        public static boolean isOpen() {
            return (db != null);
        }

        public synchronized static SingeltonDB getInstance(String username,
                String password) throws Exception {

            if (db != null) {
                throw (new Exception("The database is  open"));
            } else {
                System.out.println("The database is now open");
                singalDb = new SingeltonDB(username, password);
            }
            db.connect(username, password);
            System.out.println("The database was connected");

            return singalDb;
        }

        public synchronized static SingeltonDB getInstance() throws Exception {
            if (db == null) {
                throw (new Exception("The database is not open"));
            }

            return singalDb;
        }

        public void create(String tableName) throws Exception {
            dbLock = new ReentrantLock();
            dbLock.lock();
            db.create(tableName);
            dbLock.unlock();
        }

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

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

    }

Main

public class Main {
    public static void main(String[] args) throws Exception {

        Creator cr= new Creator(new UserContorller());
        Thread t1 = new Thread(cr);
        t1.start();
        Producer pr = new Producer(new UserContorller());
        Thread t2 = new Thread(pr);
        t2.start();

        /*
         * Consumer cn = new Consumer(new UserContorller()); Thread t2 = new
         * Thread(cn); t2.start();
         */
    }
}

class Creator implements Runnable {
    UserContorller uc;

    public Creator(UserContorller uc) {
        this.uc = uc;
    }

    @Override
    public void run() {
        try {
            uc = new UserContorller("MyAccount", "123");
            uc.createTable("table1");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class Producer implements Runnable {
    UserContorller uc;

    public Producer(UserContorller uc) {
        this.uc = uc;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                uc.saveUser("table1", i, "User", i);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

class Consumer implements Runnable {
    UserContorller uc;

    public Consumer(UserContorller uc) {
        this.uc = uc;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                System.out.println(uc.getUser("table1", i));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

Connect once, when creating the singleton (in the constructor, perhaps). Have a synchronized static method ( getInstance or something), that checks if an instance exists, creates and connects as necessary, and returns the instance. By following this protocol, you ensure that threads always get a connected Db object ready to be used.

The users will call that method to get the singleton instance, and call update or whatever they want on it, it does not need to be synchronized.

Note: The post below was written in the perspective of both users using the same credentials (hidden from them) to connect to the database. If users employ different credentials, the idea of a singleton db object is purposeless, each user should have their own connection object, and of course then connection details are passed on from the user to the Db via whatever represents the user in the program (here the thread instances apparently).

The main issue in the implementation you provided is that the getinstance method requires its caller to know the connection details, or assume that the connection has already been done. But neither threads could nor should know in advance if the Db has been opened already -- and design wise it's a mistake to hand them the responsibility of explicitely opening it. These threads are work threads, they shouldn't be concerned about Db configuration details.

The only sane way to handle this situation is to have these configuration parameters held by the Db object directly, or better yet another object in charge of providing it (it's the factory pattern).

However, if you want first your code to work with minimal changes, get rid of the parameter less getinstance method, have any thread requiring the Db object to use the remaining variant of that method, passing along the correct parameters, and change it to return the instance if it exists, or create it otherwise, without raising an exception. I believe that it's what @Dima has been trying to explain in his answer.

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