当多个线程同时访问一个可变共享变量时,尤其是有线程会修改这个变量的值,如果没有使用同步的话,就会产生安全性问题。比如,前面的那个计数器的例子,由于这个类本身不是线程安全的,它有一个共享的可变变量,当多个线程同时执行的时候,最后可能返回相同的值,这显然违背了当初的设计。
导致出现线程安全性问题主要是因为存在竞争条件。当某个计算的正确性取决于多个线程的交替执行次序时,就会发生竞争条件。最常见的竞争条件就是“先检查后执行”,即通过一个可能失效的结果来决定下一步的操作。
“先检查后执行”的一个常见例子就是延迟初始化。如果大家了解设计模式的话,应该知道单利设计模式,它采用的就是延迟初始化的思想。例如:
publicclass
privatestatic Singleton instance;
private
}
public static
if(instance == null){
instance = new
}
return instance;
}
在这个单例设计模式在中,getInstance方法中的那个if语句存在一个竞争条件:先判断instance是否为null,才决定要不要new Singleton。比如,当线程A看到instance为null,则new一个Singleton,由于线程的执行次序是不确定的,同时线程B也可能看到instance为null,也会new一个Singleton,结果创建了两个对象,这显然就不是单例的。
还有一个常见的例子是“读取--修改--写入”( 例如递增一个计数器),基于对象之前的状态来定义对象状态的转换。要递增一个计数器,你必须知道它之前的值,并确保执行更新的过程中没有其他线程会使用或修改这个值。
下面介绍一下解决线程安全性问题的办法:
1、 不在线程之间共享变量
一个类是不是线程安全的主要取决于这个类的状态,而类的状态主要取决于它的成员变量。如果一个类没有成员变量,那么我们说这个类是无状态的,它肯定是线程安全的。在方法内部定义的变量都是局部变量,每个线程都有自己的一个副本,当方法执行完毕,局部变量会自动销毁,也就是说局部变量是线程独享的。因此在设计类的时候,尽量考虑使用局部变量,而不是成员变量。
2、将状态变量声明为不可变
如果一个类必须声明成员变量的话,尽量将它声明为final,因为final不允许修改,这样就能够确保线程安全性。我们称这样的类为不可变类,不可变类肯定是线程安全的。
3、使用同步
在实际应用中,一般把类声明为无状态或不可变是比较少的,大部分都是有状态的,这样就不得不使用同步。同步是一个比较广的概念,而不仅仅是用synchronized关键字这么简单。
今天先暂时介绍到这里,后续将详细的讨论如何使用同步解决线程安全性问题。