Cha*_*hap 7 java concurrency swing buffering swingworker
我正在使用这个练习作为教学工具来帮助我学习一些Java GUI编程概念.我正在寻找的是一般性的理解,而不是一个特定问题的详细解决方案.我希望编码这个"正确"将教会我如何处理未来的多线程问题.如果这对于这个论坛来说太笼统了,那么它可能属于程序员吗?
我正在模拟读卡器.它有一个GUI,允许我们将卡加载到料斗中并按下Start等,但它的主要"客户端"是CPU,在单独的线程上运行并请求卡.
读卡器保持单个缓冲区.如果卡片请求进入且缓冲区为空,读卡器必须从料斗读取卡片(需要1/4秒,这是1962年).在将卡读入缓冲区后,读卡器将缓冲区发送到CPU,并在下一个请求之前立即启动另一个缓冲区加载操作.
如果不仅缓冲器是空的,而且料斗中没有卡,那么我们必须等到操作员将料斗放入料斗并按下Start(它始终启动缓冲加载操作).
在我的实现中,卡请求以invokeLater() Runnables在EDT上排队的形式发送到读卡器.在myRunnable.run()时间,无论是一个缓冲器将可用(在这种情况下,我们可以将其发送到CPU和开球另一个缓冲器负载操作),或缓冲区将是空的.如果它是空的怎么办?
两种可能性:(a)飞行中已经有缓冲加载操作,或(b)卡漏斗是空的(或尚未启动).在任何一种情况下,让EDT等待是不可接受的.工作(和等待)必须在后台线程上完成.
为了简单起见,我尝试生成一个SwingWorker来响应每个卡请求,而不管缓冲区的状态如何.伪代码是:
SwingWorker worker = new SwingWorker<Void, Void>() {
public Void doInBackground() throws Exception {
if (buffer.isEmpty()) {
/*
* fill() takes 1/4 second (simulated by Thread.sleep)
* or possibly minutes if we need to have another
* card deck mounted by operator.
*/
buffer.fill();
}
Card card = buffer.get(); // empties buffer
/*
* Send card to CPU
*/
CPU.sendMessage(card); // <== (A) put card in msg queue
/*
* Possible race window here!!
*/
buffer.fill(); // <== (B) pre-fetch next card
return null;
}
};
worker.execute();
Run Code Online (Sandbox Code Playgroud)
这产生了一些奇怪的时间效应 - 我怀疑,由于buffer.fill()可能发生的竞赛如下:如果在(A)和(B)之间,CPU收到了卡,发送了另一个请求,并有另一个SwingWorker线程代表它生成,然后可能有两个线程同时尝试填充缓冲区.[删除(B)的预取调用解决了这个问题.]
所以我认为为每次读取产生一个SwingWorker线程是错误的.必须在单个线程中序列化缓冲和发送卡.该线程必须尝试预取缓冲区,并且必须能够等待并恢复,如果我们用完卡并且必须等待更多的东西放入漏斗中.我怀疑SwingWorker有一个长期运行的后台线程来处理这个问题,但我还没有完成.
假设SwingWorker线程是要走的路,我该如何实现这一点,消除EDT上的延迟,允许线程阻塞等待漏斗补充,并处理缓冲区填充是否在另一个卡请求到达之前或之后完成的不确定性?
编辑:我从另一个线程得到了答案,并将在这里回顾:
而不是使用SwingWorker线程,建议我ExecutorService newSingleThreadExecutor()在开始时创建一次,并使用GUI排列冗长的方法execute(Runnable foo),如下所示(此代码在EDT中运行):
private ExecutorService executorService;
::
/*
* In constructor: create the thread
*/
executorService = Executors.newSingleThreadExecutor();
::
/*
* When EDT receives a request for a card it calls readCard(),
* which queues the work out to the *single* thread.
*/
public void readCard() throws Exception {
executorService.execute(new Runnable() {
public void run() {
if (buffer.isEmpty()) {
/*
* fill() takes 1/4 second (simulated by Thread.sleep)
* or possibly minutes if we need to have another
* card deck mounted by operator.
*/
buffer.fill();
}
Card card = buffer.get(); // empties buffer
/*
* Send card to CPU
*/
CPU.sendMessage(card); // <== (A) put card in msg queue
/*
* No race! Next request will run on same thread, after us.
*/
buffer.fill(); // <== (B) pre-fetch next card
return;
}
});
}
Run Code Online (Sandbox Code Playgroud)
这与SwingWorker的主要区别在于,这确保了只有一个工作线程.
知道内部SwingWorker使用是有帮助的ExecutorService; 为方便起见,它增加了临时EDT处理机制.只要您在EDT上更新GUI并同步对任何共享数据的访问,后者就等同于前者.
假设您使用的是模型 - 视图 - 控制器模式,此处建议您的模型是cpu的操作.虽然它可能是一个不同的类,但我看不出有任何理由在不同的线程上对读卡器进行建模.相反,让处理器模型具有读卡器模型,该模型在java.util.Timer线程上等待,在计时器触发时更新模型.让更新的模型在向EDT发布事件的正常过程中通知视图.让控制器取消并调度读卡器模型以响应视图手势.
我在附加到原始问题的"答案"中遗漏了一件事:
我Thread.sleep()通过单线程执行程序将耗时的工作(仅用于教学目的)交给后台线程.然而,出现了一个问题,因为后台线程是通过poll()作为Swing组件的数据模型的List来"读取卡片",并且引发了大量AWT数组索引超出范围异常.在几次徒劳的尝试同时通过EDT和我的后台线程同步对List的访问之后,我发誓,并将命令包装到poll()List并在一个小的Runnable()中更新GUI,并使用invokeAndWait()来导致当我的后台任务等待时,他们在EDT上运行.
这是我修改后的解决方案:
private ExecutorService executorService;
:
executorService = Executors.newSingleThreadExecutor();
:
/*
* When EDT receives a request for a card it calls readCard(),
* which queues the work to the *single* thread.
*/
public void readCard() throws Exception {
executorService.execute(new Runnable() {
public void run() {
if (buffer.isEmpty()) {
/*
* fill() takes 1/4 second (simulated by Thread.sleep)
*/
buffer.fill();
}
Card card = buffer.get(); // empties buffer
/*
* Send card to CPU
*/
CPU.sendMessage(card); // <== (A) put card in msg queue
/*
* No race! Next request will run on same thread, after us.
*/
buffer.fill(); // <== (B) pre-fetch next card
return;
}
});
}
/*
* IMPORTANT MODIFICATION HERE - - -
*
* buffer fill() method has to remove item from the list that is the
* model behind a JList - only safe way is to do that on EDT!
*/
private void fill() {
SwingUtilities.invokeAndWait(new Runnable() {
/*
* Running here on the EDT
*/
public void run() {
/*
* Hopper not empty, so we will be able to read a card.
*/
buffer = readHopper.pollLast(); // read next card from current deck
fireIntervalRemoved(this, readHopper.size(), readHopper.size());
gui.viewBottomOfHopper(); // scroll read hopper view correctly
}
});
// back to my worker thread, to do 1/4 sec. of heavy number crunching ;)
// while leaving the GUI responsive
Thread.sleep(250);
:
etc.
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1880 次 |
| 最近记录: |