Вопрос
Как создать правильный загрузчик фона в Java 8? Условия:
- данные должны быть загружены в фоновом режиме
- после загрузки данные должны отображаться
- пока данные загружены, дальнейшие запросы не принимаются
- если были запросы во время загрузки данных, то по истечении определенного времени ожидания должна быть назначена другая загрузка (например, 5 секунд)
Цель состоит в том, чтобы, например, принять запросы на перезагрузку, но не заполнить базу данных запросами.
MCVE
Вот MCVE. Он состоит из фоновой задачи, которая имитирует загрузку, просто вызывая Thread.sleep в течение 2 секунд. Задача планируется каждую секунду, что, естественно, приводит к перекрытию задач фоновой загрузки, которых следует избегать.
public class LoadInBackgroundExample {
/**
* A simple background task which should perform the data loading operation. In this minimal example it simply invokes Thread.sleep
*/
public static class BackgroundTask implements Runnable {
private int id;
public BackgroundTask(int id) {
this.id = id;
}
/**
* Sleep for a given amount of time to simulate loading.
*/
@Override
public void run() {
try {
System.out.println("Start #" + id + ": " + Thread.currentThread());
long sleepTime = 2000;
Thread.sleep( sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("Finish #" + id + ": " + Thread.currentThread());
}
}
}
/**
* CompletableFuture which simulates loading and showing data.
* @param taskId Identifier of the current task
*/
public static void loadInBackground( int taskId) {
// create the loading task
BackgroundTask backgroundTask = new BackgroundTask( taskId);
// "load" the data asynchronously
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
CompletableFuture<Void> future = CompletableFuture.runAsync(backgroundTask);
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return "task " + backgroundTask.id;
}
});
// display the data after they are loaded
CompletableFuture<Void> future = completableFuture.thenAccept(x -> {
System.out.println( "Background task finished:" + x);
});
}
public static void main(String[] args) {
// runnable which invokes the background loader every second
Runnable trigger = new Runnable() {
int taskId = 0;
public void run() {
loadInBackground( taskId++);
}
};
// create scheduler
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
ScheduledFuture<?> beeperHandle = scheduler.scheduleAtFixedRate(trigger, 0, 1, TimeUnit.SECONDS);
// cancel the scheudler and the application after 10 seconds
scheduler.schedule(() -> beeperHandle.cancel(true), 10, TimeUnit.SECONDS);
try {
beeperHandle.get();
} catch (Throwable th) {
}
System.out.println( "Cancelled");
System.exit(0);
}
}
Вывод такой:
Start #0: Thread[ForkJoinPool.commonPool-worker-2,5,main]
Start #1: Thread[ForkJoinPool.commonPool-worker-4,5,main]
Start #2: Thread[ForkJoinPool.commonPool-worker-6,5,main]
Finish #0: Thread[ForkJoinPool.commonPool-worker-2,5,main]
Background task finished:task 0
Finish #1: Thread[ForkJoinPool.commonPool-worker-4,5,main]
Background task finished:task 1
Start #3: Thread[ForkJoinPool.commonPool-worker-4,5,main]
Finish #2: Thread[ForkJoinPool.commonPool-worker-6,5,main]
Background task finished:task 2
Start #4: Thread[ForkJoinPool.commonPool-worker-6,5,main]
Start #5: Thread[ForkJoinPool.commonPool-worker-2,5,main]
Finish #3: Thread[ForkJoinPool.commonPool-worker-4,5,main]
Background task finished:task 3
Start #6: Thread[ForkJoinPool.commonPool-worker-4,5,main]
Finish #4: Thread[ForkJoinPool.commonPool-worker-6,5,main]
Background task finished:task 4
Finish #5: Thread[ForkJoinPool.commonPool-worker-2,5,main]
Background task finished:task 5
Start #7: Thread[ForkJoinPool.commonPool-worker-2,5,main]
Finish #6: Thread[ForkJoinPool.commonPool-worker-4,5,main]
Start #8: Thread[ForkJoinPool.commonPool-worker-6,5,main]
Background task finished:task 6
Start #9: Thread[ForkJoinPool.commonPool-worker-4,5,main]
Finish #7: Thread[ForkJoinPool.commonPool-worker-2,5,main]
Background task finished:task 7
Start #10: Thread[ForkJoinPool.commonPool-worker-2,5,main]
Finish #8: Thread[ForkJoinPool.commonPool-worker-6,5,main]
Background task finished:task 8
Cancelled
Цель состоит в том, чтобы, например, # 1 и # 2 пропустить, потому что # 0 все еще работает.
проблема
Где правильно установить механизм блокировки? Следует ли использовать синхронизацию? Или некоторые AtomicBoolean
? И если так, должно ли это быть внутри get()
метода или в другом месте?
java
multithreading
java-8
Roland
источник
источник
ExecutorService
с размером пула потоков 1?BlockingQueue
?Ответы:
У вас уже есть пул потоков для выполнения задачи. Это не обязательно и усложняет запуск задачи в другом асинхронном исполнителе (
ForkJoinPool
когда вы используетеCompletableFuture
)Сделай это проще:
ScheduledExecutorService обеспечит выполнение только одной задачи за раз, когда вы вызываете ее с scheduleAtFixedRate
источник
Принимая следующие требования:
Решение может быть построено на основе
Executors.newSingleThreadExecutor()
,CompletableFuture
иLinkedBlockingQueue
:После выполнения стандартный вывод будет иметь следующий вывод:
источник
Я добавил AtomicInteger, который будет действовать как счетчик для запуска задач с простыми методами lock () и unlock () с этим небольшим изменением в вашем исходном коде, который я получил:
Вот мое решение для вашей задачи:
ОБНОВИТЬ
Я изменил методы lock () и unlock () на более простую форму:
источник
Если вы понимаете, у вас есть несколько задач в фоновом режиме одновременно. так как эти задачи выполняют одну и ту же работу, вы не хотите выполнять их параллельно, вам нужна одна задача, чтобы завершить работу и поделиться ее результатами с другими. Поэтому, если вы получаете 10
CompletableFuture
одновременно, вы хотите, чтобы один из них вызвал 'reload' в db и поделился результатами выполнения с другими так, чтобы всеCompletableFuture
нормально завершалось с результатом. Я предполагаю это иза также
если мои догадки верны, вы можете попробовать мое решение.
У меня какие-то отношения между родителями и детьми между задачами. родительская задача - это та, которая действительно выполняет свою работу и делится полученным результатом со своими детьми. Дочерняя задача - это задача, которая была добавлена, когда родительская задача еще выполнялась, дочерняя задача ожидает, пока родительская задача не завершит свое выполнение. Поскольку результаты родительского задания все еще «свежие», они копируются в каждого ребенка, и все они заканчивают свое будущее.
И вот вывод:
источник
если вам нужен только один поток доступа, то простая синхронизация сделает эту работу ...
вывод:
код:
источник
Я попробовал решение, используя двойной переключатель Thread, см. Класс
BackgroundTaskDualSwitch
, он имитирует загрузку с помощьюCompletableFuture
. Идея состоит в том, чтобы позволить второй задаче подождать, пока текущая задача не будет завершена, см. Изменение вBackgroundTask
. Это гарантирует, что работает максимум один поток Thread, и максимум один поток Thread ожидает. Дальнейшие запросы пропускаются до тех пор, пока запущенная задача не будет завершена, и не станет «свободной» для обработки следующего запроса.Выход:
источник
Первый поток, который начинает выполнять дорогостоящую работу, сообщит обратным вызовом результат. Другие потоки, которые пытаются его выполнить, будут зарегистрированы в ExорогоWork.notificables, поэтому, как только дорогая работа завершит работу, поток, выполнивший эту работу, уведомит их.
Тем временем потоки проверяют результат каждые 5 секунд.
И это вывод:
источник