线程安全是开发者在开发多线程任务时最关心的问题,那么线程安全需要注意哪些呢?
一、思考:线程安全产生的原因是什么? 二、final,volatile关键字的作用? 三、1.5之前的javaDCL有什么缺陷? 四、如何编写线程安全的程序? 五、ThreadLocal使用的注意事项有哪些?
原因:可变资源(内存)线程间共享
由Java的内存模型:各线程都有自己的工作内存 和 虚拟机的主内存。 工作内存中保存了该线程使用的变量的副本,各线程对变量的所有操作必须在工作内存中进行,即操作的是变量的副本。
看下面这个过程: 例如:对于 int a = 3; 线程A、B、C 都要操作变量a++;
这时就出问题了:a = 5才对,因为A,B线程都执行了a++,两次a++。
根据线程安全原因:可变资源(内存)线程间共享可得出:
ThreadLocal:
如何使用ThreadLocal(看Loop.java中如何使用):
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
ThreadLocal原理: ThreadLocal.java
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//这个map是绑定在线程上的
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocal使用建议:
final的禁止重排序:
y不是final的,会被重排序,如果被重排序到构造函数之外,就会出现上面情况。
volatile的禁止重排序:
在JDK1.5的时候volatile的语义被增强了,增加了【禁止指令重排序】,JDK1.4之前下面的DCL单例是有问题的。
public class Singleton {
private static volatile Singleton singleton; //volatile很关键
private Singleton() {}
public static Singleton getInstance() {
if (null == singleton) { //提高程序的效率
synchronized (Singleton.class) {
if (null == singleton) { //解决多线程下的安全性问题,也就是保证对象的唯一
singleton = new Singleton();
}
}
}
return singleton;
}
}
jdk1.5之后的翻译为机器码指令是下面:
1、test(single == null)
2、lock
3、test(single == null)
4、$instance = <allocate memory> //分配内存
5、call constructor
6、singleton = $instance
7、unlock
而jdk1.4之前加volatile或者jdk1.5后不加volatile的指令可能是下面:
1、test(single == null)
2、lock
3、test(single == null)
4、$instance = <allocate memory> //分配内存
5、singleton = $instance
6、unlock
7、call constructor
结论:这样的指令造成构造函数的调用在锁外面了,当多线程时,其他线程得到锁并且判断单例不null,直接来用,但是可能这个单例并没有被构造完成,会造成不可预知的问题。
保证可见性的方法
例如a++,其实际代码如下,被拆分成了3行代码,不是原子操作。
int tmp = a;
tmp += 1;
a = tmp;
保证原子性的方法: 1、加锁,保证操作的互斥性 2、使用CAS指令(如,Unsafe.compareAndSwapInt) 3、使用原子数值类型(如,AtomicInteger) 4、使用原子属性更新器(AtoicReferenceFieldUpdater)
补充: 可重入函数,不涉及其他内存:
public static int add(int a){
return a + 2;
}