文章目录
前言
线程安全:代码同时被多个线程安全地调用。如果一段代码是安全的,那它不包含竞争条件。竞争条件只发生在多个线程更新共享资源的时候,因此当Java线程执行的时候,知道哪些资源是线程共享资源是非常重要的。
一、Local variables (本地变量/局部变量)
局部变量保存在每个线程独有的线程栈中,因此局部变量在线程之间是不共享的。也就是说所有的局部变量都是线程安全的。举例:
public void someMethod(){
long threadSafeInt = 0;
threadSafeInt++;
}
二、Local Object References (本地对象引用/局部对象引用)
本地对象引用有所不同,引用本身是不共享的,同样也是保存在线程独有的线程栈中,线程之间不共享引用。但是引用的对象不是保存在线程栈中,而是保存在主内存堆中,理论上讲所有的线程都能够访问内存堆中存储的对象(但是要有对象的引用)。
如果一个对象创建之后没有离开创建它的方法,那么是线程安全的。事实上传递这个对象的引用给其他的方法,只要这个对象的引用没有传递给其他的线程,那么这个对象都不会成为共享对象,始终是线程安全的。示例:
public void methodOne(){
LocalObject localObject = new LocalObject();
methodTwo(localObject);
}
public void methodTwo(LocalObject localObject){
localObject.setValue("value");
}
示例中:对象 localObject 在 methodOne() 方法中被创建,然后传递给 methodTwo(),localObject 没有传递给其他线程; 每个线程执行 methodOne() 时会都创建一个新的 LocalObject 对象,且 LocalObject 对象的引用都保存在各自的线程栈中,因此是 methodOne() 线程安全的,尽管 LocalObject 存在多个实例对象,但使用它们是线程安全的。
但有一种场景例外: 当某个方法将 localObject 对象的引用作为参数传递给了其他线程,那么可能会造成线程不安全。
三、Object Member Variables (对象成员变量)
对象成员变量随对象保存在堆内存中。因此当两个线程调用了某个方法,这个方法引用了同一个对象并修改了这个对象的成员变量时,那么这个方法时线程不安全的。示例:
public class NotThreadSafe{
StringBuilder builder = new StringBuilder();
public add(String text){
this.builder.append(text);
}
}
当多个线程同时调用同一个 NotThreadSafe 对象的 add() 方法时,会导致竞争条件发生。 示例:
NotThreadSafe sharedInstance = new NotThreadSafe();
new Thread(new MyRunnable(sharedInstance)).start();
new Thread(new MyRunnable(sharedInstance)).start();
public class MyRunnable implements Runnable{
NotThreadSafe instance = null;
public MyRunnable(NotThreadSafe instance){
this.instance = instance;
}
public void run(){
this.instance.add("some text");
}
}
请注意有 2 个 MyRunnable 对象共享了 sharedInstance 对象,因此当他们同时调用 sharedInstance.add() 方法时,会发生竞争条件。
然而,当 2 个线程同时调用不对对象的 add() 方法时,不会产生竞争条件。示例
new Thread(new MyRunnable(new NotThreadSafe())).start();
new Thread(new MyRunnable(new NotThreadSafe())).start();
四、线程安全规则
当你想确认你的代码访问某些资源的时候是否是线程安全的,你可以使用下面这个规则:
如果资源的创建、使用和销毁都没有离开某个方法,并且没有分享给其他线程,那么使用这个资源是线程安全的。