Мое требование примерно такое:
Я написал код, который проверяет данные в базе данных, и если данные не найдены, он использует сторонний API для отдыха для получения данных. Выполняется некоторая логика и окончательно полученные данные сохраняются в базе данных.
public void exampleMethod(){
MyObject myObj = getObjFromDatabase(); //getting from db
if (myObj==null){ //not found in db
myObj = getObjFromThirdParty(); //consuming rest api
//some logic here in case myobj is not in db....
}else{
//some other logic here in case myobj is in db...
}
saveObjInDatabase(myObj); //saving it in database
}
Мне нужно, чтобы он был сохранен в базе данных только один раз. Получение ответа от стороннего API занимает некоторое время, и этот метод выполняется из нескольких потоков. Теперь проблема в том, что мне нужно, чтобы он был сохранен в базе данных только один раз, но прежде чем один поток сможет сохранить данные в базе данных, другой поток получит значение null из базы данных, логика, которая должна выполняться только тогда, когда «данные не в базе данных» выполняется и сохраняется одни и те же данные более одного раза. (Я использую mongoDB для хранения данных) Как я могу решить эту проблему? Спасибо.
Контекст не ясен, но похоже, что несколько потоков создают myObj
, где myObj
каждого потока - это просто отдельный экземпляр объекта одно и тоже. Проще всего было бы синхронизировать exampleMethod()
.
Синхронизация exampleMethod()
, вероятно, является самым простым решением, но если существует много одновременных чтений, это может заблокировать много, и в этом нет необходимости. Потокам нужно только остановиться и подождать, если другой поток обновляет значение.
Вы спрашиваете о кешировании. Вот пример, который должен прилично работать. Он синхронизируется на getObj
в случае необходимости загрузки объекта. Если объект свежий, то getObj
возвращается очень быстро и почти не блокирует другие потоки, но если ему нужно загрузить объект, другие потоки будут ждать, пока объект не загрузится.
public class Test {
// used when you want to refresh the cached object
private static boolean refreshNext = false;
// a static reference to your object that all threads can use
private static MyObj cachedObj = null;
private static void message(String msg) {
System.out.println(
System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : " + msg);
}
private static void sleep(long milli) {
try { Thread.sleep(milli); } catch (Exception e) { }
}
// represents the object stored in the db
private static MyObj myObjInDb = null;
private static void saveObjInDb(MyObj obj) {
// TODO do real saving to db and handle errors
message("storing in db...");
myObjInDb = obj;
}
private static MyObj loadObjFromDb() {
// TODO do real loading from db and handle errors
message("getting from db...");
sleep(1000);
return myObjInDb;
}
private static MyObj loadObjFromVendor() {
// TODO do real fetching from vendor and handle errors
message("getting from vendor...");
sleep(2000);
return new MyObj();
}
private static MyObj loadObj() {
message("loading object...");
MyObj obj = loadObjFromDb();
if (obj == null) {
message("db didn't have it.");
obj = loadObjFromVendor();
saveObjInDb(obj);
}
return obj;
}
/** Returns the object, first loading and caching if needed. */
public static synchronized MyObj getObj() {
// only one thread can get the object at a time, in case it needs to be loaded.
if (cachedObj == null || refreshNext) {
// load and cache the object
cachedObj = loadObj();
refreshNext = false;
}
return cachedObj;
}
public static void exampleMethod() {
MyObj obj = getObj();
message(obj.toString());
// ... do stuff with obj
}
private static class MyObj {
public final String data = "I have data!";
@Override public String toString() { return data; }
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++)
new Thread(Test::exampleMethod).start();
}
}
Короткий ответ - «запирание». Я бы сказал, что вам, вероятно, следует выполнить блокировку на стороне базы данных, чтобы она работала, если у вас когда-либо будет несколько JVM, запускающих этот код одновременно с одной и той же базой данных. Будет ли это так? В противном случае вы можете просто использовать блокировку в своем Java-коде.