看了java并发实战的书,但又不知道如何总结对象的共享,看过一篇博客,感觉写的挺好的,也按照这种方式加上内存分析大概的总结一下。
参考:http://tutorials.jenkov.com/java-concurrency/thread-safety.html#local-variables
代码可以被多个线程安全的调用,我们就称线程是安全的。如果一段代码是线程安全的,他就不包含任何竞争条件。只有当多个线程更新和共享资源的时候才会出现竞争条件。因此了解java多线程在执行时,哪些资源是共享非常重要,我们来按照java变量类型来意义说明。
本地变量(局部变量)
本地变量被存储在每个线程自己的栈中。这意味着局部变量从来不会被多线程之间共享。也就是说局部原始变量是线程安全的。下面的例子是线程安全的局部变量,如下代码所示:
public void someMethod(){
long threadSafeInt = 0;
threadSafeInt++;
}
本地对象引用(局部对象变量引用)
局部引用对于对象来说,不同于上述的基本类型变量的引用。对象的引用是不共享的,是存储在各个线程的栈中。但是指向new出来的对象,并不存储在线程的本地栈中。所有new出来的对象都存储在heap(堆)中。
如果一个对象在方法中,也就是局部变量对象,从来没有逃逸出这个方法,那么就是线程安全的。事实上我们可以把变量传递到其他的方法,只要这些方法不会被其他线程使用到就可以,如下栗子。
public void someMethod(){
LocalObject localObject = new LocalObject();
localObject.callMethod();
method2(localObject);
}
public void method2(LocalObject localObject){
localObject.setValue("value");
}
LocalObject在例子中方法没有任何返回值,也没有传递给在 someMethod() 方法之外可以访问这个局部对象的其他对象。每个线程执行someMethod()方法将会创建自己的LocalObject实例并且指向自己的引用变量locaObject。因此我们说LocalObject在线程中是安全的。
事实上,someMethod()方法是线程安全的。即使我们把LocalObject实例作为方法的参数传递给相同的类,这也是线程安全的。
除非方法method2()传递了LocalObject作为参数,并且被其他线程访问他的实例,这种情况局部变量不是线程安全的。(画一画内存分析就清晰了)
对象的成员变量
对象的成员变量随着对象被保存在共享堆中。如果两个线程调用了同一个对象实例上的同一个方法,并更改了成员对象,那这个方法就不是安全的。如下面的代码示例。
public class NotThreadSafe{
StringBuilder builder = new StringBuilder();
public add(String text){
this.builder.append(text);
}
}
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");
}
}
然后如果两个线程在不同的实例上,调用不同实例的add()方法,就不会导致竞争条件的出现,如下我们这样调用:
new Thread(new MyRunnable(new NotThreadSafe())).start();
new Thread(new MyRunnable(new NotThreadSafe())).start();
线程控制逃逸规则
当我们想要确定代码访问某个资源是否是安全的,我们可以使用线程的逃逸规则来判断。规则如下:
1 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.
如果一个资源创建、使用和释放都在同一个线程的控制下,并且这个资源不会逃逸出这个线程的控制范围,那么对这个资源的使用就是线程安全的。
资源可以是任何共享资源,比如对象、数组、文件、数据库连接、socket等。在java中不需要程序员来显示的disposed(释放)资源。被释放就是始终没有地方使用这个对象或者把这个对象的引用设置为null。
2 即使一个对象在线程中使用时安全的,如果这个对象又指向了一个共享资源,比如文件或者数据库,我们的整体应用程序未必就是安全的。
例如,Thread 1 and thread 2 各自创建了自己的数据库连接,connection 1 和connection2 。使用每个连接的时候是安全的,但是使用连接指向的资源未必就是安全的。代码可能这样的逻辑:
check if record X exists , if not,insert record X (检查X是否存在,如果不存在,则插入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
上述可能会发生在任何一个可共享资源上。因此非常重要的去区分,被线程控制的是对象资源本身,还是这个资源的引用。(like a database connection does)
总结:
我们在看一个对象是否是共享资源的时候,不但要看这个资源是在内存中的栈中还是在内存中的堆中,也要看线程使用的是对象还是对象的引用。同时也要看多线程是如何使用这个资源的,一个资源可能在堆中,但是没有被多个线程共享,他是安全的,例如每个线程都new一个资源。也可能一个资源在堆中,但是可能被其他资源共享,例如对象的成员变量。
所以控制线程安全,要合理的控制对象的本身,同时也要看我们是如何调用这个对象的。安全这个名词,在线程这块是动态的,不是静态的。
下一篇java并发编程--volatitle关键字