本文翻译自http://tutorials.jenkov.com/java-util-concurrent/atomicreferencearray.html,人工翻译,仅供学习交流。
线程安全和共享资源
多线程调用安全的代码被称为线程安全。如果代码的部分是线程安全的,则说明不含竞态条件。只有在多个线程更新共享资源时才会出现竞争条件。因此,了解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");
}
本例中的LocalObject实例没有从方法中返回,也不会传递给任何其他可以从someMethod()方法外部访问的对象。每个执行someMethod()方法的线程都将创建自己的LocalObject实例并将其分配给localObject引用。因此LocalObject在这里的使用是线程安全的。事实上,整个someMethod()方法是线程安全的。即使LocalObject实例作为参数传递给同一类中的其他方法,或者在其他类中,它的使用是线程安全的。当然唯一的例外是,,如果其中一个方法以LocalObject作为参数被调用,以允许从其他线程访问它的方式存储LocalObject实例。
对象的成员变量
对象成员变量(字段)与对象一起存储在堆上。如果两个线程调用同一个对象实例上的一个方法并且这个方法更新对象成员变量,该方法不是线程安全的。下面是一个非线程安全的方法示例:
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");
}
}
请注意两个MyRunnable实例如何共享同一个NotThreadSafe实例。因此,当它们调用NotThreadSafe实例上的add()方法时,就会导致竞争条件。但是,如果两个线程在不同的实例上同时调用add()方法,这样就不会导致竞态。以下是之前的示例,但略有修改:
new Thread(new MyRunnable(new NotThreadSafe())).start();
new Thread(new MyRunnable(new NotThreadSafe())).start();
现在这两个线程都有自己的NotThreadSafe实例,因此它们对add方法的调用不会相互干扰。代码不再具有竞争条件。因此,即使一个对象不是线程安全的,它仍然可以以一种不会导致竞态条件的方式使用。
线程控制转义规则
当试图确定您的代码对某个资源的访问是否线程安全时,你可以使用线程控制转义规则:
If a resource is created, used and disposed within
the control of the same thread,
and never escapes the control of this thread,
the use of that resource is thread safe.
资源可以是任何共享资源,如对象、数组、文件、数据库连接、套接字等。在Java中,你并不总是显式地释放对象,所以"dispose "的意思是失去对该对象的引用或使其无效。
即使对象的使用是线程安全的,如果该对象指向文件或数据库等共享资源,您的应用程序作为一个整体可能不是线程安全的。例如,如果线程1和线程2都创建了自己的数据库连接,连接1和连接2,每个连接本身的使用是线程安全的。但是连接所指向的数据库的使用可能不是线程安全的。例如,如果两个线程都执行如下代码:
check if record X exists
if not, insert record X
如果两个线程同时执行,他们正在检查的记录X碰巧是相同的记录,有一个风险是两个线程最终都插入它。这就是:
Thread 1 checks if record X exists. Result = no
Thread 2 checks if record X exists. Result = no
Thread 1 inserts record X
Thread 2 inserts record X
这也可能发生在操作文件或其他共享资源的线程上。区分线程控制的对象是否是资源很重要,或者它只是引用资源(就像数据库连接那样)。