Java 为什么可以';异步线程是否同时修改ArrayList?
我正在研究线程,在这里遇到了这个片段: 我们创建并启动两个相同的Java 为什么可以';异步线程是否同时修改ArrayList?,java,multithreading,asynchronous,Java,Multithreading,Asynchronous,我正在研究线程,在这里遇到了这个片段: 我们创建并启动两个相同的java.lang.Threads,让它们连续修改ArrayList,而不做任何非线程安全的事情,因为我们只是在做研究 两个线程只是同一类的实例NoteThread。在run()方法中有两个操作: 将(项目)添加到列表中 从列表中删除(0) 这两个操作在1000次迭代中执行 public class Solution { public static void main(String[] args) { new
java.lang.Thread
s,让它们连续修改ArrayList
,而不做任何非线程安全的事情,因为我们只是在做研究
两个线程只是同一类的实例NoteThread
。在run()
方法中有两个操作:
将(项目)
添加到列表中从列表中删除(0)
李>
这两个操作在1000次迭代中执行
public class Solution {
public static void main(String[] args) {
new NoteThread().start();
new NoteThread().start();
}
public static class Note {
public static final List<String> notes = new ArrayList<String>();
public static void addNote(String note) {
notes.add(0, note);
}
public static void removeNote(String threadName) {
String note = notes.remove(0);
if (note == null) {
System.out.println("Another thread has already deleted the note");
} else if (!note.startsWith(threadName)) {
System.out.println("Thread [" + threadName + "] has deleted [" + note + "]");
}
}
}
public static class NoteThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
Note.addNote(getName() + "-Note" + i);
Note.removeNote(getName());
}
}
}
}
我们可以100%确定,一个项目的创建总是在同一个线程中删除之前进行的,因此我假设当一个线程想要删除一个项目但找不到一个项目时,不可能遇到这种情况
更新:非常清楚地解释了程序执行顺序的概念(我在问题中最初没有提到,但无论如何都是这么说的),最重要的是,他回答了这个问题:
如果线程1
添加了一些内容,线程2
将看不到中的更改
有些情况
在Java中,在内部,我们修改的每个对象实际上都在与它一起工作的每个线程中有它的缓存副本。因为这个例子没有做任何关于线程安全的事情,所以我们修改的数组也被缓存到每个线程中。既然如此,有可能:
注意,这只是我的理解,我不是专家
线程1
将数组复制到其缓存中线程2
将数组复制到其缓存中线程1
将一个项添加到其缓存的数组中李>
线程2
将一个项添加到其缓存的数组中线程1
从缓存的数组中删除项目线程1
将其缓存的数组刷新到实际数组中JVM
传播更改并将实际数组上载到该对象的所有用户,即线程2
。因此,第二个线程现在拥有当前为空的数组的更新版本线程2
从缓存的数组中删除项目索引自动边界异常:索引:0,
大小:-1
您正在同时修改数据结构
ArrayList
A1
-添加到线程1,A2
-添加到线程2,R1
-删除int线程1,R2
-删除到线程2,
。
在一次迭代中,您可以得到:
A1>R1>A2>R2
A1>A2>R1>R2
A1>A2>R2>R1
A1>R1
和A2>R2
调度程序也可以在线程1中执行多次迭代,并且只能在切换到线程2之后执行
因此,没有理由期望在两个线程中有任何操作顺序。在这种情况下,您所拥有的只是一个线程中添加和删除的编程顺序。但你没有“先发生后发生”的关系。更多信息请参阅。
但是最好先有一个基本的了解。如果您想同时使用列表,您必须选择线程安全的实现,例如
java.util.Vector
:
public static final List<String> notes = new Vector<String>();
public static final List notes=new Vector();
或者,您可以使用
java.util.concurrent.CopyOnWriteArrayList
p.s.我知道您永远不应该扩展线程
是否要同步?因为两个线程都访问相同的资源,即notes在并发修改相同的列表后,您期望得到什么?在盲目运行代码片段之前,尝试阅读一些理论。您需要同步对此列表的访问,以避免不可预知的行为。两个线程都是异步运行的,并且在它们运行时您没有对其进行显式控制。这就是为什么你会有不同的行为。我知道在这种情况下需要同步。但问题是对相反的理解。这个片段实际上没有任何实际意义。我只是不明白,如果一个线程创建一个项目,然后每个项目删除一个项目,那么如何才能在另一个线程之前清空列表?我觉得要清空列表,你需要删除比创建更多的内容,因为你创建了一个,你删除了一个,其他线程也会这样做,所以你永远不会发现列表是空的。他们可以删除彼此的便条,没错,但他们怎么会遇到它已经是空的???我猜,它比我想象的要多。我现在将从中提取出来,并接受原因为“没有使用线程安全方法”。当我学到足够的知识后,我将阅读JLS,并找出一切发生的确切原因。但是谢谢你的回答!SPASIBOW当你仔细检查你引用的文档时。。。那么这两个语句确实有一个“发生在之前”的顺序。请参阅@GhostCat没有顺序:add
在线程A中,HBadd
在线程B中。您在说什么?线程A中的指令顺序,先添加后删除?@SamKruglov不,您使用的是非线程安全的数据结构。这意味着,如果线程1添加了一些东西——线程2——在某些情况下将看不到更改。想象一个简单的动态数组。有时你必须调整它的大小。假设一个线程调整了大小,但另一个线程没有得到“更新”的数组。-第二个线程将做同样的调整大小,你会遇到麻烦。这只是一个例子,出于所有目的,Vector
应被视为depr
public static final List<String> notes = new Vector<String>();