原子性
原子是世界上的最小单位,具有不可分割性。比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++; 这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。java的concurrent包下提供了一些原子类,我们可以通过阅读API来了解这些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference
可见性
可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。另一个线程马上就能看到。比如:用volatile修饰的变量,就会具有可见性。volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。所以对其他线程是可见的。但是这里需要注意一个问题,volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性。比如 volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就这这个操作同样存在线程安全问题。
对于非volatile类型的double和long变量,jvm允许将64位分解为两个32位的操作,如果读取一个非volatile类型的变量时,如果对该变量的读操作和写操作不在同一个线程中执行,那么可能会读到的某个值的高32位和另一个值的低32位。
对象的发布与逸出
“发布(Publish)“一个对象是指使对象能够在当前作用域之外的代码中使用。可以通过 公有静态变量,非私有方法,构造方法内隐含引用 三种方式。
如果对象构造完成之前就发布该对象,就会破坏线程安全性。当某个不应该发布的对象被发布时,这种情况就被称为逸出(Escape)。
-
公有静态变量
public static Set<string> mySet;
public void initialize() {
mySet = new HashSet<string>();
}
当发布某个对象时,可能会间接地发布其他对象。如果将一个 String 对象添加到集合 mySet 中,那么同样会发布这个对象,因为任何代码都可以遍历这个集合,并获得对这个 String 对象的引用,然后就可以修改String对象。
-
非私有方法
class UnsafeState {
private String[] states = new String[] { "AK", "AL" };
public String[] getStates() {
return states;
}
}
如果按照上诉方法来发布 states,就会出问题,因为任何调用者都能修改这个数组的内容。数组 states 已经溢出了它所在的作用域了,因为这个本应是私有的变量已经被发布了。当私有变量被发布出去之后,这个类就无法知道”外部方法“会进行何种操作.例如:
String[] states = getStates();
states[0] = "bk";
这样就修改了这个数组里面的对象。如果是多线程的话 states在多线程中应用执行,就会有风险。
-
构造方法内隐含引用
public class ThisEscape {
public ThisEscape(EventSource source) {
source.registerListener(new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
});
}
}
这种在构造函数里面启用线程是不对的。
public class SafeListener{
private final EventListener listener;
private SafeListener(){
listener = new EventListener(){
public void onEvent(Event e){
doSomething(e);
}
};
}
public static SafeListener newInstance(EventSource source){
SafeListener safe = new SafeListener();
source.registerListener(safe.listener);
return safe;
}
}
线程封闭
当访问共享的可变数据时,通常需要使用同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步。这种技术被称为线程封闭(Thread Confinement),它是实现线程安全型的最简单方式之一。当某个对象封闭在一个线程中时,这种用法将自动实现线程安全性,即使被封闭的对象本身不是线程安全的。
线程封闭的一种常见的应用是 JDBC 的 Connection 对象。JDBC 规范并不要求 Connection 对象必须是线程安全的。在典型的服务器应用程序中,线程从连接池中获得一个 Connection 对象,并且用该对象来处理请求,使用完之后再将对象返还给连接池。由于大多数请求(例如 Servlet 请求或 EJB 调用等)都是由单个线程采用同步的方式来处理,并且在 Connection 对象返回之前,连接池都不会将它分配给其它线程,因此,这种连接管理模式在处理请求时隐含的将 Connection 对象封闭在线程中。
Java 语言及其核心库提供了一些机制来帮助维持线程封闭性,例如局部变量和 ThreadLocal 类,即便如此,程序员仍然需要确保封闭在线程中的对象不会从线程中逸出。
ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。