谈到线程安全问题,我们先说说什么是共享资源。所谓共享资源,就是说该资源被多个线程所持有或者说多个线程都可以去访问该资源。
线程安全问题是指当多个线程同时读写一个共享资源并且没有任何同步措施时,导致出现脏数据或者其他不可预见的结果的问题,如图 2-3 所示。
图 2-3
在图 2-3 中,线程 A 和线程 B 可以同时操作主内存中的共享变量,那么线程安全问题和共享资源之间是什么关系呢?是不是说多个线程共享了资源,当它们都去访问这个共享资源时就会产生线程安全问题呢?答案是否定的,如果多个线程都只是读取共享资源,而不去修改,那么就不会存在线程安全问题,只有当至少一个线程修改共享资源时才会存在线程安全问题。最典型的就是计数器类的实现,计数变量 count 本身是一个共享变量,多个线程可以对其进行递增操作,如果不使用同步措施,由于递增操作是获取—计算—保存三步操作,因此可能导致计数不准确,如下所示。
假如当前 count=0,在 t1 时刻线程 A 读取 count 值到本地变量 countA。然后在 t2 时刻递增 countA 的值为 1,同时线程 B 读取 count 的值 0 到本地变量 countB,此时 countB 的值为 0(因为 countA 的值还没有被写入主内存)。在 t3 时刻线程 A 才把 countA 的值 1 写入主内存,至此线程 A 一次计数完毕,同时线程 B 递增 CountB 的值为 1。在 t4 时刻线程 B 把 countB 的值 1 写入内存,至此线程 B 一次计数完毕。这里先不考虑内存可见性问题,明明是两次计数,为何最后结果是 1 而不是 2 呢?其实这就是共享变量的线程安全问题。那么如何来解决这个问题呢?这就需要在线程访问共享变量时进行适当的同步,在 Java 中最常见的是使用关键字 synchronized 进行同步,下面会有具体介绍。