Java Swing和并发-在操作发生之前休眠请求
我正在尝试开发一种方法,在最短的时间内安排一个Java Swing和并发-在操作发生之前休眠请求,java,multithreading,swing,sleep,event-dispatch-thread,Java,Multithreading,Swing,Sleep,Event Dispatch Thread,我正在尝试开发一种方法,在最短的时间内安排一个可运行的。 代码应该从发出请求开始,并倒计时,直到经过一段时间,然后执行Runnable。 但我还需要可以发出多个请求,对于每个新请求,延迟将在执行Runnable之前更新 目标是实现以下行为: 当用户滚动JList时,JList的JScrollPane垂直滚动条中的调整侦听器将在执行Runnable之前请求延迟。 每次用户滚动时,都会发出一个新的请求,因此延迟会被延长。 请求立即返回,以便EDT被阻止的时间最少。 因此,Runnable的等待和执行
可运行的
。
代码应该从发出请求开始,并倒计时,直到经过一段时间,然后执行Runnable
。
但我还需要可以发出多个请求,对于每个新请求,延迟将在执行Runnable
之前更新
目标是实现以下行为:
当用户滚动JList
时,JList
的JScrollPane
垂直滚动条中的调整侦听器将在执行Runnable
之前请求延迟。
每次用户滚动时,都会发出一个新的请求,因此延迟会被延长。
请求立即返回,以便EDT被阻止的时间最少。
因此,Runnable
的等待和执行应该发生在不同的线程
(而不是EDT)中。
从上次发出的请求经过最短时间后,执行Runnable
我需要这种行为,因为JList
将包含数千个图像缩略图。
我不想预加载JList
中的所有缩略图,因为它们可能无法放入内存。
我也不想在用户滚动时加载缩略图,因为他可以任意快速滚动让我把它放进去。
因此,我只想在用户在JList
中的单个位置等待/停留一段时间(例如500毫秒、1秒或介于两者之间)后开始加载缩略图
我尝试的是用workerThread
s创建一个完全手工制作的调度器。
按照我的努力,在评论中有相关解释:
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.
}
}
它的用法将类似于以下代码(如果可能的话,我希望在您可能的答案中保持相关性):
但是该代码为每个请求创建一个新的线程
(并中断最后一个)。
我不知道这是否是一个好的实践,因此我也尝试使用一个线程
,该线程循环睡眠,直到从上次发出请求到请求时间过去:
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) {
}
}
}
但是我也不知道这是否是一个好的实践,我想这也会消耗更多的CPU时间
我的问题不是为了最生态的解决方案,而是是否有更好/更正式的方法来实现这一点,同时减少混乱/代码。
例如,我应该使用a、a还是a?但是怎么做呢?
我猜包裹里的东西应该是答案
正如你所能想象的那样,我并不真的在乎延迟的超精确性
评论中关于实现相同目标的其他方法的任何建议也将是好的
我不是真的要求调试,但我也不认为这个问题应该转移到,因为我要求的是一个替代/更好的解决方案
我更希望使用Java8(以及更高版本,如果不可能使用Java8)
谢谢。这里是一个使用摆动计时器的示例。按下按钮将重新启动2秒延迟
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Delay extends JPanel {
Timer timer;
int presses = 0;
public Delay() {
setLayout(new BorderLayout());
JButton b = new JButton("Sleep 2 seconds");
JLabel label = new JLabel("The app is currently asleep.");
add(b, BorderLayout.CENTER);
add(label, BorderLayout.SOUTH);
b.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
timer.restart();
presses++;
}
});
timer = new Timer(2000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
label.setText("Time expired after " + presses + " presses");
}
});
timer.start();
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
final JFrame jf = new JFrame();
JPanel panel = new Delay();
jf.add(panel);
jf.pack();
jf.setVisible(true);
jf.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(final WindowEvent arg0) {
System.exit(0);
}
});
}
});
}
}
当发出新请求时,使用java.swing.Timer并调用Timer.restart()。@FredK感谢您的评论。听起来很简单。我不知道这件事,也没想到会这么简单我会测试它。GUI用户是否可以选择一个或多个选项与其他JComponent一起使用,这样他就不必滚动浏览成千上万的图像?@GilbertLeBlanc问题是,用户在从目录加载这些图像后,会一个接一个地对其进行分类。我的意思是,它们不会以任何方式预先分类。如果是的话,我确实可以让他先选择一个类别,然后给他看图片。根据你的评论,一次加载50张左右的图片,让应用程序为进行分类的用户提供一个短暂的休息时间,可能是更好的用户体验。
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...
});
});
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Delay extends JPanel {
Timer timer;
int presses = 0;
public Delay() {
setLayout(new BorderLayout());
JButton b = new JButton("Sleep 2 seconds");
JLabel label = new JLabel("The app is currently asleep.");
add(b, BorderLayout.CENTER);
add(label, BorderLayout.SOUTH);
b.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
timer.restart();
presses++;
}
});
timer = new Timer(2000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
label.setText("Time expired after " + presses + " presses");
}
});
timer.start();
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
final JFrame jf = new JFrame();
JPanel panel = new Delay();
jf.add(panel);
jf.pack();
jf.setVisible(true);
jf.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(final WindowEvent arg0) {
System.exit(0);
}
});
}
});
}
}