gpt4 book ai didi

java - Java:ConcurrentListModel-试图绕过Swing线程安全规则,我是否陷入困境或发疯

转载 作者:行者123 更新时间:2023-11-29 03:06:17 25 4
gpt4 key购买 nike

因此,首先,我绝对知道Swing的主要线程安全规则:所有GUI组件交互都必须在事件分发线程上进行。但是,由于我一直在编写代码并按照Model-View-Controller准则进行工作,所以我一直想找到一种方法来解决Swing组件数据模型。

警告,这是一篇很长的文章,但是我想真正地解释一下我的思考过程。我在这里很有创造力,主要是想获得反馈,以了解这是否切实可行。

我现在正在使用的程序现在使用一个 Controller 类,该类实现了ActionListener来监听UI类中组件的事件。调用actionPerformed()方法时,它将通过执行程序将ActionEvent传递到另一个线程,该执行程序在该事件执行程序中解析事件并更新我的程序的模型。这些模型触发PropertyChangeEvent,这些事件包装在SwingUtilities.invokeLater中,因此它们对GUI所做的更改将在EDT上进行。

到目前为止,一切似乎都是100%的犹太洁食。

但是然后我有了这个JList。我要做的是从EDT修改其组件,然后对其进行更新。我已经提出了几个想法,实际上我正在探索实现这一目标的多种途径。特殊的添加/删除PropertyChangeEvents或传递新列表以用每个事件替换现有列表只是我的一些想法。

但是我在这篇文章中提出了我最疯狂的想法:一个线程安全的ConcurrentListModel,可以通过任意数量的线程进行修改,但仍然符合Swing的线程安全规则。

所以,让我解释一下:

对于ListModel,它仅通过三种方式与JList本身进行交互。这些方式中的每一种都为并发访问提出了一个独特的问题:

1)从基础集合(getElement,getSize)中检索信息。 JList需要能够访问基础集合以在其 View 中正确显示其值。此基础集合上的任何同步都可能导致UI的响应性问题,但是如果此集合被多个线程修改,则结果将是任何数量的线程一致性问题。

2)添加/删除ListDataListeners。当然,要添加的监听器是BasicListUI.ListDataHandler,它链接到JList本身。 AbstractListModel实现允许将任意数量的ListDataListeners添加到EventListenerList中。同样,由于响应问题,此列表在EDT需要访问时无法同步,但是如果由多个线程对其进行了修改,则无法保证此列表状态的一致性。

3)触发ListDataEvents。 JList没有链接到模型中的基础集合,只有模型本身。因此,每当模型对基础集合进行更改时,它都必须将ListDataEvent激发到JList。然后,JList调用我前面提到的getElement/size方法。这些事件必须在EDT上触发。

因此,乍一看,这是一个与JList的关系使其无法从EDT进行修改的组件。除了...我是一个固执的SOB,我想我已经找到了解决方法。我将要描述的所有内容,请在做出判断时以任何明显的错误回应。

我列出的三件事,它们不要求ListModel仅由EDT触及,它们仅要求其中的某些部分仅由EDT触及。具体来说,它要求此类的STATE FIELDS(基础列表和EventListenerList)在EDT上保持线程局部。该类的所有方法都以一种或另一种方式与这两个字段进行交互。只要这些方法最终对这些字段产生的影响仅在EventDispatchThread上执行,则调用它们的线程无关紧要。

此外,由于仅在单个线程上修改了类的状态,所以根本不需要任何同步,因为状态字段保持为线程局部的。这样可以避免因Swing组件等待锁定而导致的潜在响应错误。

那么,这一切最终意味着什么?我将展示我的示例代码,以便您可以真正了解我的意思。这是按照我的设计的一个简单的add()方法(注意:我还希望最终产品遵循Collections API和List接口(interface),因此要使用方法签名):

public boolean add(final E element){
final boolean result = false;
if(SwingUtilities.isEventDispatchThread()){
result = swingAdd(element);
}
else{
SwingUtilities.invokeLater(new Runnable(){
@Override
public void run(){
result = swingAdd(element);
}
});
}

return result;
}

private boolean swingAdd(E element){
int index = list.size(); //The current size will be size - 1 after the add, aka the index of the element being added.
boolean result = list.add(element);
fireContentsChanged(this, index, index);

return result;
}

从这个例子中,您可以看到我是如何做到这一点的。每个公共(public)方法都与一个单独的私有(private)“swing”方法配对。 public方法执行检查以查看是否在EDT上调用了它。如果是这样,它将仅运行swing方法。如果不是,则使用SwingUtilities.invokeLater(),然后调用swing方法。无论哪种方式,仅在EDT上调用swing方法。并且由于swing方法是其中的唯一部分,它实际上与基础列表进行交互,因此该列表保持线程局部状态。

使用此设计的类的唯一主要要求是仅在EventDispatchThread上实例化该类。此后可以将其传递给其他线程或在其他线程上使用,但是其基础状态的线程安全性的最后保证需要来自EDT上的实例化。通过遵循该规则,此类将确保通过以下方式对已附加到JList的列表模型进行线程安全,摆动安全并发访问:

1)基础列表的状态是线程本地的。包装在此模型中的列表将仅在EventDispatchThread上与之交互,因此保留了swing的线程约束。

2)不需要同步。同步旨在保护可变状态,但是此类的可变状态将仅通过在EventDispatchThread上进行访问而受到保护。通过防止需要获取锁,这既促进了并发访问,又避免了UI滞后。

3)EventListenerList将在与基础列表相同的级别上受到同等保护。所有添加/删除操作最终将仅在EDT上执行。

4)仅在EDT上触发ListDataEvents,以确保与其关联的swing组件仅在EDT上进行交互。

5)如果在EDT上访问了此类,则不会使用SwingUtilities.invokeLater()。这样可以避免在没有操作的情况下不必要的已排队的可运行对象
要求可以执行。

现在,在我要编写类本身之前,我仍然需要解决三件事。有两个是相对较小的细节,但一个可能是一个问题:

1)(次要)如果将ListDataEvent触发到非摆动组件,该怎么办。我不希望EDT陷入潜在的后台进程。如果我不能为此找到一个线程安全的解决方案,那么我将切断从非EDT线程中添加ListDataListener的功能,并采取其他步骤来限制可以使用的监听器对象的类型。有了这个,这就是为什么我认为这是一个小问题。

2)(次要)我还想用此类完全实现List接口(interface)。我还没有真正弄清楚它的迭代器和列表迭代器部分,但是话又说回来,我还没有花太多时间考虑那部分。最终,我可以不理会这些方法,而让这些方法返回null或抛出不受支持的异常。

3)(潜在问题)对基础列表中包含的对象的引用将不具有任何已知的线程安全保证。也就是说,不管它们是否是线程安全的,即使其在列表中的位置保持不变,也可以从多个线程中修改单个元素的状态。而且,这样的对象可以使用同步来访问其内部状态,但是这种事情听起来像是ListModels必须摆动处理的相当标准的事情。

无论如何,暂时将其搁置以处理其他问题,但需要反馈。我是在追求某种东西,还是完全出于我的该死的想法?哈哈。

最佳答案

因为这是easy to get wrong,所以建议您使用 java.util.concurrent 提供的现有功能。

对于 JList 的特殊情况,请使用SwingWorker的实现 Future 来更新ListModel的实现中的process(),这将在事件分发线程上调用。使用SwingWorker的适当实现作为FIFO缓冲区来构造BlockingQueue。然后,在实现take()publish()doInBackground()新到达的元素。请注意,take()阻止doInBackground()是可以的;用户不会注意到。现在,您程序中的任何其他线程都可以add()offer()put()队列中的元素,并完全确保该元素以最小的等待时间出现在JList中。

对于您的BlockingQueue,请考虑为元素的固定大小的缓冲区提供一个ArrayBlockingQueue。考虑元素的有限大小缓冲区的LinkedBlockingDeque。如果其他线程可以阻塞,请使用put();否则,请使用offer()。需要检查时使用add();并在需要捕获IllegalStateException的情况下使用ojit_code。

关于java - Java:ConcurrentListModel-试图绕过Swing线程安全规则,我是否陷入困境或发疯,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32039719/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com