本文为转载学习html
原文连接:http://tutorials.jenkov.com/java-concurrency/thread-safety.htmljava
译文连接:http://ifeve.com/thread-safety/数据库
容许被多个线程同时执行的代码称做线程安全的代码。线程安全的代码不包含竞态条件。当多个线程同时更新共享资源时会引起竞态条件。所以,了解Java线程执行时共享了什么资源很重要。数组
局部变量存储在线程本身的栈中。也就是说,局部变量永远也不会被多个线程共享。因此,基础类型的局部变量是线程安全的。下面是基础类型的局部变量的一个例子:安全
public void someMethod(){ long threadSafeInt = 0; threadSafeInt++; }
对象的局部引用和基础类型的局部变量不太同样。尽管引用自己没有被共享,但引用所指的对象并无存储在线程的栈内。全部的对象都存在共享堆中。若是在某个方法中建立的对象不会逃逸出(译者注:即该对象不会被其它方法得到,也不会被非局部变量引用到)该方法,那么它就是线程安全的。实际上,哪怕将这个对象做为参数传给其它方法,只要别的线程获取不到这个对象,那它还是线程安全的。下面是一个线程安全的局部引用样例:app
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经过某些方法被传给了别的线程,那它就再也不是线程安全的了。学习
对象成员存储在堆上。若是两个线程同时更新同一个对象的同一个成员,那这个代码就不是线程安全的。下面是一个样例:ui
public class NotThreadSafe{ StringBuilder builder = new StringBuilder(); public add(String text){ this.builder.append(text); } }
若是两个线程同时调用同一个NotThreadSafe
实例上的add()方法,就会有竞态条件问题。例如:this
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()方法时会形成竞态条件。spa
固然,若是这两个线程在不一样的NotThreadSafe实例上调用call()方法,就不会致使竞态条件。下面是稍微修改后的例子:
new Thread(new MyRunnable(new NotThreadSafe())).start(); new Thread(new MyRunnable(new NotThreadSafe())).start();
如今两个线程都有本身单独的NotThreadSafe对象,调用add()方法时就会互不干扰,不再会有竞态条件问题了。因此非线程安全的对象仍能够经过某种方式来消除竞态条件。
线程控制逃逸规则能够帮助你判断代码中对某些资源的访问是不是线程安全的。
若是一个资源的建立,使用,销毁都在同一个线程内完成, 且永远不会脱离该线程的控制,则该资源的使用就是线程安全的。
资源能够是对象,数组,文件,数据库链接,套接字等等。Java中你无需主动销毁对象,因此“销毁”指再也不有引用指向对象。
即便对象自己线程安全,但若是该对象中包含其余资源(文件,数据库链接),整个应用也许就再也不是线程安全的了。好比2个线程都建立了各自的数据库链接,每一个链接自身是线程安全的,但它们所链接到的同一个数据库也许不是线程安全的。好比,2个线程执行以下代码:
检查记录X是否存在,若是不存在,插入X
若是两个线程同时执行,并且碰巧检查的是同一个记录,那么两个线程最终可能都插入了记录:
线程1检查记录X是否存在。检查结果:不存在 线程2检查记录X是否存在。检查结果:不存在 线程1插入记录X 线程2插入记录X
一样的问题也会发生在文件或其余共享资源上。所以,区分某个线程控制的对象是资源自己,仍是仅仅到某个资源的引用很重要。