Я пытаюсь разработать способ планирования Runnable
по истечении наименьшего количества времени. Код должен начинаться с выполнения запроса и обратного отсчета до истечения определенного времени, а затем выполнить Runnable
. Но мне также нужно, чтобы было сделано более одного запроса, и для каждого нового запроса задержка будет продлеваться до выполнения Runnable
.
Цель состоит в том, чтобы добиться следующего поведения: Когда пользователь прокручивает JList
, слушатель настройки в вертикальной полосе прокрутки JList
JScrollPane
запросит задержку перед выполнением Runnable
. Каждый раз, когда пользователь прокручивает новый запрос, задержка возобновляется. Запрос немедленно возвращается, так что EDT блокируется на минимальное время. Таким образом, ожидание и выполнение Runnable
должно происходить в другом Thread
(чем EDT). По истечении наименьшего количества времени после последнего выполненного запроса Runnable
выполняется.
Мне нужно такое поведение, поскольку JList
будет содержать многие тысячи миниатюр изображений. Я не хочу предварительно загружать все миниатюры в JList
, потому что они могут не помещаться в память. Я не хочу загружать миниатюры, когда пользователь прокручивает, потому что он может делать произвольные быстрые прокрутки, позвольте мне поставить его. Поэтому я хочу начать загружать миниатюры только после того, как пользователь ждет / обосновывается в одном месте в JList
в течение некоторого времени (например, 500 мс, 1 секунда или что-то между).
Что Я попытался создать полностью 1010 * ручной планировщик с рабочим Thread
с. Далее следуют мои усилия с соответствующими пояснениями в комментариях:
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.LongConsumer;
public class SleepThenActScheduler {
public class WorkerThread extends Thread {
//How long will we be waiting:
private final TimeUnit sleepUnit;
private final long sleepAmount;
public WorkerThread(final TimeUnit sleepUnit,
final long sleepAmount) {
this.sleepUnit = sleepUnit;
this.sleepAmount = sleepAmount;
}
public TimeUnit getSleepUnit() {
return sleepUnit;
}
public long getSleepAmount() {
return sleepAmount;
}
@Override
public void run() {
try {
if (sleepUnit != null)
sleepUnit.sleep(sleepAmount); //Wait for the specified time.
synchronized (SleepThenActScheduler.this) {
if (t == this && whenDone != null) { //If we are the last request:
//Execute the "Runnable" in this worker thread:
whenDone.accept(System.currentTimeMillis() - start);
//Mark the operation as completed:
whenDone = null;
t = null;
}
}
}
catch (final InterruptedException ix) {
//If interrupted while sleeping, simply do nothing and terminate.
}
}
}
private LongConsumer whenDone; //This is the "Runnable" to execute after the time has elapsed.
private WorkerThread t; //This is the last active thread.
private long start; //This is the start time of the first request made.
public SleepThenActScheduler() {
whenDone = null;
t = null;
start = 0; //This value does not matter.
}
public synchronized void request(final TimeUnit sleepUnit,
final long sleepAmount,
final LongConsumer whenDone) {
this.whenDone = Objects.requireNonNull(whenDone); //First perform the validity checks and then continue...
if (t == null) //If this is a first request after the runnable executed, then:
start = System.currentTimeMillis(); //Log the starting time.
else //Otherwise we know a worker thread is already running, so:
t.interrupt(); //stop it.
t = new WorkerThread(sleepUnit, sleepAmount);
t.start(); //Start the new worker thread.
}
}
И его использование будет выглядеть следующим кодом (который я хотел бы сохранить в ваших возможных ответах, если это возможно):
SleepThenActScheduler sta = new SleepThenActScheduler();
final JScrollPane listScroll = new JScrollPane(jlist);
listScroll.getVerticalScrollBar().addAdjustmentListener(adjustmentEvent -> {
sta.request(TimeUnit.SECONDS, 1, actualElapsedTime -> {
//Code for loading some thumbnails...
});
});
Но этот код создает новый Thread
для каждого запроса (и прерывает последний). Я не знаю, является ли это хорошей практикой, поэтому я также попытался использовать один Thread
, который зацикливается до тех пор, пока не истекло запрошенное время с момента последнего выполненного запроса:
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.LongConsumer;
public class SleepThenActThread extends Thread {
public static class TimeAmount implements Comparable<TimeAmount> {
private final TimeUnit unit;
private final long amount;
public TimeAmount(final TimeUnit unit,
final long amount) {
this.unit = unit;
this.amount = amount;
}
public void sleep() throws InterruptedException {
/*Warning: does not take into account overflows...
For example what if we want to sleep for Long.MAX_VALUE days?...
Look at the implementation of TimeUnit.sleep(...) to see why I am saying this.*/
if (unit != null)
unit.sleep(amount);
}
public TimeAmount add(final TimeAmount tammt) {
/*Warning: does not take into account overflows...
For example what if we want to add Long.MAX_VALUE-1 days with something else?...*/
return new TimeAmount(TimeUnit.NANOSECONDS, unit.toNanos(amount) + tammt.unit.toNanos(tammt.amount));
}
@Override
public int compareTo(final TimeAmount tammt) {
/*Warning: does not take into account overflows...
For example what if we want to compare Long.MAX_VALUE days with something else?...*/
return Long.compare(unit.toNanos(amount), tammt.unit.toNanos(tammt.amount));
}
}
private static TimeAmount requirePositive(final TimeAmount t) {
if (t.amount <= 0) //+NullPointerException.
throw new IllegalArgumentException("Insufficient time amount.");
return t;
}
private LongConsumer runnable;
private TimeAmount resolution, total;
public SleepThenActThread(final TimeAmount total,
final TimeAmount resolution) {
this.resolution = requirePositive(resolution);
this.total = requirePositive(total);
}
public synchronized void setResolution(final TimeAmount resolution) {
this.resolution = requirePositive(resolution);
}
public synchronized void setTotal(final TimeAmount total) {
this.total = requirePositive(total);
}
public synchronized void setRunnable(final LongConsumer runnable) {
this.runnable = Objects.requireNonNull(runnable);
}
public synchronized TimeAmount getResolution() {
return resolution;
}
public synchronized TimeAmount getTotal() {
return total;
}
public synchronized LongConsumer getRunnable() {
return runnable;
}
public synchronized void request(final TimeAmount requestedMin,
final LongConsumer runnable) {
/*In order to achieve requestedMin time to elapse from this last made
request, we can simply add the requestedMin time to the total time:*/
setTotal(getTotal().add(requestedMin));
setRunnable(runnable);
if (getState().equals(Thread.State.NEW))
start();
}
@Override
public void run() {
try {
final long startMillis = System.currentTimeMillis();
TimeAmount current = new TimeAmount(TimeUnit.NANOSECONDS, 0);
while (current.compareTo(getTotal()) < 0) {
final TimeAmount res = getResolution();
res.sleep();
current = current.add(res);
}
getRunnable().accept(System.currentTimeMillis() - startMillis);
}
catch (final InterruptedException ix) {
}
}
}
(Примечание: второй подход не полностью отлажен, но я думаю, что вы поняли идею.)
И его использование будет выглядеть следующим образом:
SleepThenActThread sta = new SleepThenActThread(new TimeAmount(TimeUnit.SECONDS, 1), new TimeAmount(TimeUnit.MILLISECONDS, 10));
final JScrollPane listScroll = new JScrollPane(jlist);
listScroll.getVerticalScrollBar().addAdjustmentListener(adjustmentEvent -> {
sta.request(new TimeAmount(TimeUnit.SECONDS, 1), actualElapsedTime -> {
//Code for loading some thumbnails...
});
});
Но я не знаю если это тоже хорошая практика, и она также потребляет больше процессорного времени, я думаю.
Мой вопрос, хотя и не для самого экологичного решения, но есть ли существует лучший / более формальный способ достижения это с меньшим количеством суматохи / кода. Например, я должен использовать java.util.Timer
, javax.swing.Timer
или ScheduledExecutorService
? Но как? Я предполагаю, что что-то в пакете java.util.concurrent
должно быть ответом.
Меня не особо волнует сверхточность задержки, как вы можете себе представить.
Любые рекомендации в комментариях о других подходах к достижению той же цели также были бы хорошими.
Я на самом деле не прошу отладки, но я также не думаю, что этот вопрос следует перенести в Код Просмотрите , потому что я прошу альтернативное / лучшее решение.
Я бы предпочел, чтобы это было в Java 8 (и выше, если невозможно с 8).
Спасибо.