这是《头先Java》一书中的一个任务,要求读者找出以下代码块中发生了什么:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TwoThreadsWriting {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(2);
Data data = new Data();
threadPool.execute(()-> addLetterToData('a', data));
threadPool.execute(()-> addLetterToData('A', data));
threadPool.shutdown();
}
private static void addLetterToData(char letter, Data data) {
for(int i = 0; i < 26; i++){
data.addLetter(letter++);
try {
Thread.sleep(50);
} catch (InterruptedException ignored) {}
}
System.out.println(Thread.currentThread().getName() + data.getLetters());
System.out.println(Thread.currentThread().getName() + " size: " + data.getLetters().size());
}
}
class Data {
private final List<String> letters = new ArrayList<>();
public List<String> getLetters() {return letters;}
public void addLetter(char letter){
letters.add(String.valueOf(letter));
}
}
我期望输出列表包含52个这样的字符
['a', 'A', 'b', 'B', ...]
但有时元素数小于52,字母
中缺少一些字符,例如这里('K'
缺失):
pool-1-thread-2[A, a, B, b, c, C, d, D, e, E, f, F, G, g, H, h, i, I, J, j, k, L, l, m, M, N, n, O, o, p, P, q, Q, r, R, s, S, t, T, u, U, V, v, w, W, X, x, y, Y, Z, z]
pool-1-thread-1[A, a, B, b, c, C, d, D, e, E, f, F, G, g, H, h, i, I, J, j, k, L, l, m, M, N, n, O, o, p, P, q, Q, r, R, s, S, t, T, u, U, V, v, w, W, X, x, y, Y, Z, z]
pool-1-thread-2 size: 51
pool-1-thread-1 size: 51
我不明白为什么会发生这种情况。我尝试通过打印addLet
的局部变量字母
的值来调试它,但它只向我显示实际上添加了'K'
。
我知道这一切的发生仅仅是因为使用了非线程安全的ArrayList
或addMail
没有被声明为同步
,但我想首先了解这里发生了什么。
ArrayList
不是线程安全的。如果两个线程试图同时调用add()
,任何事情都可能发生。从Arraylist留档:
请注意,此实现不是同步的。如果多个线程并发访问一个ArrayList实例,并且其中至少有一个线程在结构上修改了列表,则必须在外部同步。