代码被多线程同时的调用是安全的称之为线程安全。如果一段代码是线程安全的,那么它就不包含竞态条件。竞态条件只是发生在多线程更新共享资源的时候。因此知道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引用。因此这种使用是线程安全的。
事实上,这整个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实例的。因此,当他们调用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中,你不能总是明确的销毁对象,以至于“销毁的”意味着对象丢失或者空的引用。
甚至如果一个对象的使用是线程安全的,如果那个对象指向了一个共享的资源像一个文件或者数据库,你的应用作为一个整体可能就不是线程安全的了。例如,如果线程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
这个也会发生在操作在文件或者其他的共享资源的线程上。因此去区分被一个线程控制的对象是否是一个资源,或者仅仅是这个资源(像数据库连接)的引用是重要的。
以上就是Java 线程全和共享资源的内容,更多相关内容请关注PHP中文网(www.php.cn)!