高并发下隐蔽的错误案例

并发下的ArrayList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ArrayListMultiThreadTest {

static ArrayList<Integer> a1 = new ArrayList<>(10);

public static void main(String[] args) throws InterruptedException {

Thread t1 = new Thread(new AddThread());
Thread t2 = new Thread(new AddThread());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(a1.size());

}

public static class AddThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000000; i++) {
a1.add(i);
}
}
}
}

运行这段代码,期望各添加1000 000个元素,但是遇到两种非预期结果:

  • 程序抛出异常 Exception in thread “Thread-0” java.lang.ArrayIndexOutOfBoundsException: 823
  • ArrayList大小为1000141

改进方式:ArrayList换为Vector

并发下的HashMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class HashMapMultiThreadTest {

static Map<String, String> map = new HashMap<>();

public static void main(String[] args) throws InterruptedException {

Thread t1 = new Thread(new AddThread(0));
Thread t2 = new Thread(new AddThread(1));
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(map.size());

}

public static class AddThread implements Runnable {
int start = 0;

public AddThread(int start) {
this.start = start;
}

@Override
public void run() {
for (int i = start; i < 10000; i += 2) {
map.put(Integer.toString(i), Integer.toBinaryString(i));
}
}
}
}

运行这段代码,期望各添加10000个元素,但是遇到两种非预期结果:

  • 程序陷入死循环。在JDK 8以后规避了这个问题
  • HashMap大小为9365

改进方式:HashMap换为ConcurrentHashMap

错误的加锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class BadLockOnIntegerTest implements Runnable {
public static Integer i = 0;
static Map<String, String> map = new HashMap<>();
static BadLockOnIntegerTest instance = new BadLockOnIntegerTest();

public static void main(String[] args) throws InterruptedException {

Thread t1 = new Thread(new BadLockOnIntegerTest());
Thread t2 = new Thread(new BadLockOnIntegerTest());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);

}

@Override
public void run() {
for (int j = 0; j < 1000; j++) {
synchronized (i) {
i++;
}
}
}
}

运行这段代码,期望输出2000,但是返回1415,原因是Integer属于不变对象。i++ 在真实执行时变成了i=Integer.valueOf(i.intValue()+1);

导致每次加锁加到了不同的对象实例上。

改进方式:synchronized(i)修改为synchronized(instance)