
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人
2.3 Java中的线程安全问题
谈到线程安全问题,我们先说说什么是共享资源。所谓共享资源,就是说该资源被多个线程所持有或者说多个线程都可以去访问该资源。
线程安全问题是指当多个线程同时读写一个共享资源并且没有任何同步措施时,导致出现脏数据或者其他不可预见的结果的问题,如图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进行同步,下面会有具体介绍。