文章目录

  • 前言
  • 一、Local variables (本地变量/局部变量)
  • 二、Local Object References (本地对象引用/局部对象引用)
  • 三、Object Member Variables (对象成员变量)
  • 四、线程安全规则



前言

线程安全:代码同时被多个线程安全地调用。如果一段代码是安全的,那它不包含竞争条件。竞争条件只发生在多个线程更新共享资源的时候,因此当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();

四、线程安全规则

当你想确认你的代码访问某些资源的时候是否是线程安全的,你可以使用下面这个规则:

如果资源的创建、使用和销毁都没有离开某个方法,并且没有分享给其他线程,那么使用这个资源是线程安全的。