目录:
前面
我们知道,当不同线程访问同一数据或内存时,就可能会发生交错(interleaving)或竞争(Race conditions)关系。每当这种情况发生时,程序就会发生一些意想不到的bug,而且一旦出现线程相关的bug是很难调试的。
为了避免这些问题的发生,就需要保证线程安全。保证的方式主要有四种,下面我们一起来看一下。
Interleaving and Race Condition
介绍四种线程安全的方法前,首先先来学习两个概念:Interleaving and Race Condition(交错和竞争)
Interleaving(交错)
交错,顾名思义,就是说在线程运行的过程中,多个线程同时运行相互交错。而且,由于线程运行一般不是连续的,那么就会导致线程间的交错。可以说,所有线程安全问题的本质都是线程交错的问题。
Race Condition(竞争)
竞争是发生在线程交错的基础上的。当多个线程对同一对象进行读写访问时,就可能会导致竞争的问题。程序中可能出现的一种问题就是,读写数据发生了不同步。例如,我要用一个数据,在该数据修改还没写回内存中时就读取出来了,那么就会导致程序出现问题。
程序运行时有一种情况,就是程序如果要正确运行,必须保证A线程在B线程之前完成(正确性意味着程序运行满足其规约)。当发生这种情况时,就可以说A与B发生竞争关系。
Confinement(限制数据共享)
保证线程安全的一个最简单也是最直接的方法就是限制数据的共享,即把数据放到单个线程中,避免在可变数据类型上发生竞争关系。其中的核心思想就是不让其他线程直接读或写这个线程中的数据。
使用局部变量保证线程安全
限制数据共享主要是在线程内部使用局部变量,因为局部变量在每个函数的栈内,每个函数都有自己的栈结构,互不影响,这样局部变量之间也互不影响。
如果局部变量是一个指向对象的引用,那么就需要检查该对象是否被限制住,如果没有被限制住(即可以被其他线程所访问),那么就没有限制住数据,因此也就不能用这种方法来保证线程安全。
范例:
public class Factorial {
/**
* Computes n! and prints it on standard output.
* @param n must be >= 0
*/
private static void computeFact(final int n) {
BigInteger result = new BigInteger("1");
for (int i = 1; i <= n; ++i) {
System.out.println("working on fact " + n);
result = result.multiply(new BigInteger(String.valueOf(i)));
}
System.out.println("fact(" + n + ") = " + result);
}
public static void main