一、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;
}
}