一、synchronized加锁
public class Student {
private static Student student;
private Student(){}
public static synchronized Student getInstance(){
if (student == null) {
student = new Student();
}
return student;
}
}
这种方法可以保证线程安全,但性能会非常差,特别是在并发情况下,当一个线程执行这个方法的时候,其他线程都会被阻塞(JDK1.6之前,1.6之后会进行自旋,利用CAS操作不断获取锁,次数不一定(自适应自旋锁),仍获取不到锁后才会阻塞)。
二、
public class Student {
private static Student student;
private Student(){}
public static Student getInstance(){
if (student == null) {
synchronized (Student.class){
student = new Student();
}
}
return student;
}
}
这种方法只有在student对象为空时才会创建对象,细化了锁的力度,但在并发情况下,线程A,B同时执行这个方法,同时进行判断,都不为空,A线程得到锁,初始化,B线程自旋,等A线程释放锁后,B线程接着进行初始化,A B两个线程得到的不是同一个对象。
三、
public class Student {
private static Student student;
private Student(){}
public static Student getInstance(){
if (student == null) {
synchronized (Student.class){
if (student == null) {
student = new Student();
}
}
}
return student;
}
}
那利用双重检查判断会怎么样呢?B线程得到锁后初始化前仍会判断student对象是否为空,这时student对象已经初始化了,判断结果为false,B不会再次创建对象。解决了线程安全问题。
四、真的是这样么?
创建一个对象分为三步
1.分配内存空间
2.初始化对象
3.将内存空间的地址赋值给对象的引用。
*其中2、3步执行时虚拟机是会重排序的。
若A线程执行时JVM将2 3步重排序,那么此时对象的易用指向的内存空间仅仅只是一个地址,这时候B线程进行第一次为空判断时,发现不为空,会将对象的引用返回。
既然错误是由指令重排序造成的,那么我们只有禁止JVM对这个对象创建时指令重排序即可。可以使用volatile关键字。
public class Student {
private static volatile Student student;
private Student(){}
public static Student getInstance(){
if (student == null) {
synchronized (Student.class){
if (student == null) {
student = new Student();
}
}
}
return student;
}
}