线程安全和共享资源

能够同时被多个线程安全地调用的代码,就是线程安全。如果一段代码是线程安全的,就说明它没有竞态。竞态只会在多线程更新共享资源的时候出现。所以,知道 Java 在执行的时候是共享了什么资源是很重要的。

局部变量

局部变量存储在每个线程自己的栈中。也就是说局部变量不会再多个线程中共享。也意味着本地的基本类型的变量是线程安全的。

public void someMethod() {
  	long threadSafeInt = 0;
  	threadSafeInt++;
}

局部对象引用

局部对象引用有些不同。尽管引用没有被共享,但是引用指向的对象不是存储在线程的栈中,而是存放在共享堆中。

如果一个对象没有从创建该对象的方法逃离,它就是线程安全的。实际上,你可以把这个对象传递给其他方法或者对象使用,只要这个对象没有被传递到其他线程就行。

下面是一个线程安全的局部对象的例子

public void someMethod() {
  	LocalObject localObject = new LocalObject();
  	localObject.callMethod();
  	method2(localObject);
}

public void method2(LocalObject localObject) {
  	localObject。setValue("value");
}

对象的成员变量

对象的成员变量和对象一起存放在堆中。因此,如果两个县城调用了同一个对象中的方法,并且该方法改变了成员变量的值,那么这个方法就不是线程安全的。

下面例子中的方法不是线程安全的

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(shareInstance)).start();
new Thread(new MyRunnable(shareInstance)).start();

public class MyRunnable implements Runnable {
  	NotThreadSafe instance = null;
  
  	public MyRunnable(NotThreadSafe instance) {
      	this.instance = instance;
  	}
  
  	public void run() {
      	this.instance.add("some text");
  	}
}

可以看到,两个线程共享的是同一个 NotThreadSafe 实例,因此当它们调用 add() 方法时,就导致了竞态。

如果两个线程调用的是不同的实例的话,即使这两个线程是同时调用,就不会产生竞态了,因此上面的代码可以这么修改

new Thread(new MyRunnable(new NotThreadSafe())).start();
new Thread(new MyRunnable(new NotThreadSafe())).start();

所以,即使一个对象不是线程安全的,也可以换一种方法来避免竞态。

线程控制逃逸规则

如果你想判断你的代码访问资源的时候是否是线程安全的,可以借助县城控制逃逸规则:

如果资源的创建、使用和销毁都是在同一个线程内,并且从不脱离线程的控制。那么对该资源的使用就是线程安全的。

不过,即使对象的使用时线程安全的,但是如果对象指向的是一个共享资源,比如文件或者数据库,那么你的应用整体上来说就不是线程安全了。比如线程1和线程2都创建了一个数据库连接:connection 1 和 connection 2。connection 1 和 connection 2 的使用时线程安全的,但是connection 1 和 connection 2 指向的数据库也许不是线程安全的。比如两个线程都执行如下操作

检查记录 X 是否存在
如果不存在,就插入记录 X

如果两个线程同时执行,就有能出现同时插入的隐患

线程 1 检查记录 X 是否存在:不存在
线程 2 检查记录 X 是否存在:不存在
线程 1 插入记录 X
线程 2 插入记录 X

这种情况也有可能发生在线程操作文件或者其他共享资源的时候。所以,区分对象代表的是资源本身,还是指向资源的引用(比如数据库连接这样的对象)。

原文地址:https://www.cnblogs.com/okadanana/p/5894638.html