多线程会有哪些问题

不积跬步,无以至千里。不积小流,无以成江海。

竞态

竞态是指计算的正确性依赖于相对时间顺序或者线程的交错。 竞态不一定就导致结果不正确,它只是不排除结果时而正确时而错误的可能。

原子性

锁可以有效的保证一段代码的原子性。但使用时需要注意锁的范围,下面的代码展示了锁范围运用不当造成的后果。

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
32
33
34
35
36
37
38
39
40
41
42
43
示例:
public static void main(String[] args) throws InterruptedException {
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(20, 20, 3,
            TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1000)
    );

    MyTask myTask = new MyTask();
    for (int i=0; i<1000; i++){
        threadPool.execute(myTask);
    }

    threadPool.shutdown();
    if (threadPool.awaitTermination(5,TimeUnit.SECONDS)){
        myTask.getResult();
    }else{
        System.out.println("超时");
    }
}

// 自定义任务处理
class MyTask implements Runnable{
    private Integer num = 0;
    private List<Integer> numList = new ArrayList<>(1000);

    @Override
    public void run() {
        Integer numCopy = num;
        synchronized (numList){
            num = numCopy + 1;
            numList.add(num);
        }
    }

    public void getResult(){
        HashSet<Integer> numSet = new HashSet<>(numList);
        System.out.println("numList.size():" + numList.size());
        System.out.println("numSet.size():" + numSet.size());
    }
}

输出:
numList.size()1:1000
numSet.size()1:944

使用 HashSet 进行去重后发现元素不足1000,说明在获取 numCopy 值时出现了重复。

可见性

关键字:volatile

可见性可以保证一个线程读取到变量相对新的值,但不能保证线程能够读取到相应变量的最新值。

有序性

volatile、synchronization 关键字都可以阻止重新排序。

示例

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
32
示例:
public static void main(String[] args) throws InterruptedException {
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(20, 20, 3,
            TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(200)
    );

    Vector<Integer> vector = new Vector(2000);
    List synchronizedList = Collections.synchronizedList(new ArrayList(2000));
    List<Integer> list = new ArrayList<>(2000);
    for (int i=0; i<2000; i++){
        int finalI = i;
        threadPool.submit(()->{
            vector.add(finalI);
            synchronizedList.add(finalI);
            list.add(finalI);
        });
    }

    threadPool.shutdown();
    if (threadPool.awaitTermination(5,TimeUnit.SECONDS)){
        System.out.println("vector.size():" + vector.size());
        System.out.println("synchronizedList.size():" + synchronizedList.size());
        System.out.println("list.size():" + list.size());
    }else{
        System.out.println("超时");
    }
}
    
输出:
vector.size():2000
synchronizedList.size():2000
list.size():1999

上述示例中演示了使用线程不安全的集合可能带来的后果。尽管它不是百分百发生,但在多线程中应当使用线程安全的对象或者使用排它锁。