Java线程安全问题
线程安全问题是一个老生常谈的问题,那么多线程环境下究竟有那些问题呢?这么说吧,问题的形式多种多样的,归根结底的说是共享资源问题,无非可见性与有序性问题。
1. 可见性
可见性是对于内存中的共享资源来说。线程作为单一的控制流,在运行的程序内线程必须拥有一些资源作为开销。例如线程的堆栈和私有的程序计数器,线程之间的堆栈是不共享的,每个线程都有自己的堆栈,当然在Java中对于线程私有的内存区域只有程序执行栈与程序计数器。每个线程的运算操作都在自己的执行栈中进行。正是由于这样的结构设计,所以对于内存我们可以分为共享内存(Java中的堆)和工作内存(线程执行栈)。
对于共享内存而言,线程并不会直接去操作它,线程对于数据的运算也并不是直接发生在共享内存中的。这里有一个值拷贝的过程,线程在执行时会将用到的共享数据从共享内存中拷贝到工作内存中,从工作内存中完成计算之后,再复制到共享内存中。
所以当多线程操作共享内存时,一个线程修改了主内存的内容,那么其他线程是否能察觉到主内存的变化呢?对于可见性有一个经典的ABA问题。其实造成ABA问题的根本原因是因为内存的可见性问题。当一个线程修改了内存值之后,其他线程并不能立刻感知到。
2. 有序性
谈到有序性就要说起操作系统的调度算法了。我们知道线程是操作系统所能调度的最小单元,既然线程归操作系统调度,那么它是如何调度的呢?不同应用场景下的调度算法不太一致,常见的调度算法有时间片轮转算法与多级反馈队列,一般的分时操作系统都会采用时间片轮转算法进行线程的调度。
所谓时间片轮转算法,就是给每个线程一定的执行时间片。当这个线程的时间片用完之后就会调度另一个线程执行。通过这个算法的特点我们便知道,如果使用这样的算法,那么程序执行的单位变成了时间片,而并不是一次程序执行。正是这样就不能保障在时间片使用完毕后,程序能够按照正常的执行逻辑停止运行交出使用权。也就是说程序停止的位置具有不确定性。这样一来顺序的控制流就变得无序了。而无序也无伤大雅,重点是对于共享资源的访问,不能无序,要有序访问共享资源。
同时,有序性的另一个层面是线程同步问题。线程的调度由操作系统负责,多个线程执行的顺序我们无法得知。这样线程执行顺序的的无序有时也会带来问题。