我正在研究线程并在这里遇到了这个片段:
我们创建并启动两个相同的java.lang.Thread
并让他们连续修改ArrayList
,而不做任何关于这是非线程安全的事情,因为我们只是在进行研究。
两个线程只是同一个类NoteThread
的实例。在run()
方法中有两个操作:
add(item)
到列表remove(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());
}
}
}
}
有时,当列表为空时,它可以抛出IndexOutOfBoundsException: Index: 0, Size: -1
并且我无法理解这是如何可能的。
输出示例:
Thread [Thread-1] has deleted [Thread-0-Note597] Another thread has already deleted the note Thread [Thread-0] has deleted [Thread-1-Note558] Another thread has already deleted the note Thread [Thread-1] has deleted [Thread-0-Note635] Another thread has already deleted the note Thread [Thread-0] has deleted [Thread-1-Note580]
我们可以100%确定,项目的创建总是在它被删除到同一个线程之前发生,所以我假设当线程想要删除项目时不可能遇到这种情况但没有找到一个。
更新:Sergey Rybalkin已经非常清楚地解释了程序执行顺序的概念(我最初在问题中没有提及但无论如何都是这样),最重要的是,他已经回答了问题:
如果
Thread 1
添加了某些内容,Thread 2
将无法看到更改 某些情况。
在Java中,在内部,我们修改的每个对象实际上都在每个使用它的线程中都有一个缓存副本。因为该示例没有对线程安全做任何事情,所以我们修改的数组也被缓存到每个线程。现在,既然如此,有可能:
注意,这就是我理解的方式,我不是专家
Thread 1
将数组复制到其缓存中。Thread 2
将数组复制到其缓存中。Thread 1
将一个项添加到其缓存的数组中。 Thread 2
将一个项添加到其缓存数组中。Thread 1
从其缓存数组中删除项目。Thread 1
将其缓存的数组刷新到实际数组中。JVM
传播更改并将实际数组上传到该对象的所有用户 - Thread 2
。因此,第二个线程现在具有当前为空的数组的更新版本。Thread 2
从其缓存数组中删除项目。IndexOutOfBoundsException: Index: 0,
Size: -1
答案 0 :(得分:2)
您正在同时修改数据结构ArrayList
如果我们命名A1
- 添加线程1,A2
- 添加线程2,R1
- 删除int thread 1,R2
- 删除线程2和以前>
。
在一次迭代中,你可以得到:
A1 > R1 > A2 > R2
A1 > A2 > R1 > R2
A1 > A2 > R2 > R1
我们始终只知道A1 > R1
和A2 > R2
调度程序也可以在线程1中执行多次迭代,并且只能在切换到线程2之后执行。
因此,没有理由期望在两个线程中进行任何操作顺序。在这种情况下,您拥有的只是在单个线程中添加和删除的编程顺序。但是你没有关系发生的事情。请参阅JLS 17中的详情。 但最好首先有一个基本的理解。
答案 1 :(得分:0)
如果要同时使用列表,则必须选择线程安全实现,例如java.util.Vector
:
public static final List<String> notes = new Vector<String>();
或者,您可以使用java.util.concurrent.CopyOnWriteArrayList
。