我有一个线程安全问题/问题关于CommonsWare 5.9事件总线的例子。
在我看来,model
ArrayList
从多个线程访问的方式有问题。如果这有效,我很想知道为什么。
此代码执行model
的声明、初始化和填充。
private static final String[] items= { "lorem", "ipsum", "dolor",
"sit", "amet", "consectetuer", "adipiscing", "elit", "morbi",
"vel", "ligula", "vitae", "arcu", "aliquet", "mollis", "etiam",
"vel", "erat", "placerat", "ante", "porttitor", "sodales",
"pellentesque", "augue", "purus" };
private ArrayList<String> model=new ArrayList<String>();
private boolean isStarted=false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
if (!isStarted) {
isStarted=true;
new LoadWordsThread().start();
}
}
public ArrayList<String> getModel() {
return(model);
}
class LoadWordsThread extends Thread {
@Override
public void run() {
for (String item : items) {
if (!isInterrupted()) {
model.add(item);
EventBus.getDefault().post(new WordReadyEvent());
SystemClock.sleep(400);
}
}
}
}
它每400毫秒唤醒一次,并向model
添加一个条目。model
的使用有点复杂,因为我没有现成的代码可以发布。它在ArrayAdapter
的某个地方。模型的消费是由对ArrayAdapter.notifyDataSetChanged()
的调用触发的。
我的问题是model
是在一个线程(不是Android UI线程)中写入的,但消费发生在UI线程上。model
ArrayList
的所有元素都是不可变的,这很有帮助,但对我来说似乎仍然不对。
如果这段代码实际上是正确的,我想知道为什么。谢谢你,李。
更新:我认为我的问题不在于事件总线部分。我想我明白了。它几乎更多的是一个java问题,我在该模型中被写入一个线程(worker线程,上面的代码),而理论上它可以同时从另一个线程(UI线程)中读取。在我看来,在模型上需要某种类型的并发访问控制。一个同步块或并发ArrayList
。但模型的最终消费者是我在应用程序中无法控制的代码,它在ArrayAdapter
某处。我不能给它添加同步。一旦用模型初始化ArrayAdapter
, ArrayAdapter
就拥有它,并将从UI线程访问它。我不确定非ui线程可以以这种方式修改模型。
你是正确的-这个示例应用程序是有缺陷的。我试图尽量减少代码,但我做得太过了。
需要做的两个修改是:
-
AsyncDemoFragment
应该在模型ArrayList<String>
的副本上工作,从当时的模型初始化,而不是实际上是模型 -
我应该在
WordReadyEvent
中提供单词,以允许AsyncDemoFragment
更新其模型副本,除了触发notifyDataSetChanged()
这将允许后台线程在后台线程上更新和维护模型主副本,让UI线程知道所需的更改,并允许UI线程检索该模型的副本供UI使用。
在其他情况下,拥有共享的同步模型也不是不可能的。在这种情况下,这是不切实际的,除非kcoppock建议创建一个定制的适配器类,在这种情况下,这就像用别克打苍蝇一样。
我将在下次图书更新中修复这个问题。
顺便说一句,我问你是否读过这一章的原因很简单,因为有些人只根据审查回购来问问题,我想知道你是否读过样本附带的材料。我的评论可以被理解为,如果你读了这一章,你就会得到你的问题的答案,我绝对不是那个意思。如果我糟糕的措辞给你带来了任何问题,我向你道歉。
谢谢你指出这个问题!