餓漢式
// 餓漢式單例
public class Hungry {
//構造器私有
private Hungry(){
}
// 一上來就把這個類載入了
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
// 餓漢式單例
public class Hungry {
// 這4組資料非常耗記憶體資源,餓漢式一上來就把所有的記憶體裡面的東西全部載入進來了,就存在這個空間
// 但這個空間現在是沒有使用的,可能會造成浪費空間
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
//構造器私有
private Hungry(){
}
// 一上來就把這個類載入了
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
餓漢式單例可能會造成浪費空間,所以想要用的時候再去建立這個物件,平時就先放在這個地方,於是就出現了懶漢式!
懶漢式
// 懶漢式單例
public class LazyMan {
// 構造器私有
private LazyMan(){
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if (lazyMan==null){
lazyMan = new LazyMan();
}
return lazyMan;
}
}
它是有問題的,單執行緒下確實單例ok,多執行緒併發就會出現問題!
測試
// 懶漢式單例
public class LazyMan {
// 構造器私有
private LazyMan(){
System.out.println(Thread.currentThread().getName()+":: ok");
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if (lazyMan==null){
lazyMan = new LazyMan();
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
LazyMan.getInstance();
}).start();
}
}
}
發現單例有問題,每次結果可能都不一樣!
解決
// 懶漢式單例
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+":: ok");
}
private static LazyMan lazyMan;
// 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式
public static LazyMan getInstance(){
if (lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 0; i < 10 ; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
但在極端情況下還是可能出現問題
經歷三個步驟:
1、 分配記憶體空間
2、 執行構造方法,初始化物件
3、 把這個物件指向這個空間
有可能會發生指令重排的操作!
比如,期望它執行 123 ,但是它真實可能執行132,比如第一個A執行緒過來執行了132,先分配空間再吧這個空間佔用了,佔用之後再去執行構造方法,如果現在突然來了個B執行緒,由於A已經指向這個空間了,它會以為這個 lazyMan 不等於 null ,直接return ,此時lazyMan還沒有完成構造,所以必須避免這個問題!
必須加上volatile
// 懶漢式單例
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+":: ok");
}
// 避免指令重排
private volatile static LazyMan lazyMan;
// 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式
public static LazyMan getInstance(){
if (lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 0; i < 10 ; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
靜態內部類
// 靜態內部類
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
也是單例模式的一種,不安全!
單例不安全 反射
// 懶漢式單例
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+":: ok");
}
private volatile static LazyMan lazyMan;
// 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式
public static LazyMan getInstance(){
if (lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan = new LazyMan(); //不是一個原子性操作
}
}
}
return lazyMan;
}
//反射
public static void main(String[] args) throws Exception {
LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true); // 無視了私有的構造器
// 通過反射建立物件
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
結論:反射可以破壞這種單例
解決
// 懶漢式單例
public class LazyMan {
private LazyMan(){
synchronized (LazyMan.class){
if (lazyMan!=null){
throw new RuntimeException("不要試圖使用反射破環 異常");
}
}
System.out.println(Thread.currentThread().getName()+":: ok");
}
private volatile static LazyMan lazyMan;
// 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式
public static LazyMan getInstance(){
if (lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan = new LazyMan(); //不是一個原子性操作
}
}
}
return lazyMan;
}
//反射
public static void main(String[] args) throws Exception {
LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true); // 無視了私有的構造器
// 通過反射建立物件
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
但是如果都用反射建立物件的情況下,還是會破環單例!
測試
解決
// 懶漢式單例
public class LazyMan {
// 標誌位
private static boolean abc = false;
private LazyMan(){
synchronized (LazyMan.class){
if (abc==false){
abc=true;
}else {
throw new RuntimeException("不要試圖使用反射破環 異常");
}
}
System.out.println(Thread.currentThread().getName()+":: ok");
}
private volatile static LazyMan lazyMan;
// 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式
public static LazyMan getInstance(){
if (lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan = new LazyMan(); //不是一個原子性操作
}
}
}
return lazyMan;
}
//反射
public static void main(String[] args) throws Exception {
//LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true); // 無視了私有的構造器
// 通過反射建立物件
LazyMan instance2 = declaredConstructor.newInstance();
LazyMan instance1 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
但是如果被人知道 abc
這個變數,也可以破環!
單例又被破環了!
看一下原始碼
它說不能使用反射破環列舉,列舉是jdk1.5出現的,自帶單例模式!
測試,寫一個列舉類
// enum 本身就是一個class類
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
檢視它的原始碼
試圖破環!
// enum 本身就是一個class類
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws Exception {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
它竟然說我現在的這個列舉類中沒有空參構造器!
然後就去原始碼裡分析!
找到這個class檔案!利用javap反編譯一下!
發現這個也顯示有一個空參構造,證明這個也不對,用第三方的工具檢視!
利用它再吧class檔案生成java檔案!
開啟這個java檔案
證明是idea和原始碼騙了我!
再次嘗試破環!
// enum 本身就是一個class類
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws Exception {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
結論:反射無法破環列舉類!