相信大家面試的時候,經常會被問到,23種設計模式,你熟悉哪寫,手寫個單例啥的,當然了,這是對於小程序員的面試,大佬請忽略
單例模式呢,顧名思義就是這個對象只會創建一次。因此他的構造方法必須私有。
那麼這隻創建一次的對象等你用的時候,又怎麼用呢?肯定是需要一個方法返回提供同一個對象,也就是唯一的對象。
單例模式
單例模式(Singleton Pattern): 確保某一個類只有一個實例,而且自行實例化並向整個系統提供這個實例,這個類稱爲單例類,它提供全局訪問的方法。單例模式是一種對象創建型模式。
單例模式有三個要點:
- 構造方法私有化;
- 實例化的變量引用私有化;
- 獲取實例的方法共有
因此代碼我們可以這樣寫(通過學習觀看,總共7種寫法):
1、餓漢式
package com.app.singlecase;
/**
* @ClassName: HungryMan
* @Description 單例模式餓漢式
* @Date:2019/9/29 11:05
* @Author: lk
* @see
* @since JDK 1.8
*/
public class HungryMan {
//一個對象
private final static HungryMan HUNGRY_MAN=new HungryMan();
//私有構建公開方法
private HungryMan(){
}
//公開方法 獲取這個對象
public static HungryMan getHungryMan(){
return HUNGRY_MAN;
}
}
優點: 簡單,使用時沒有延遲;在類裝載時就完成實例化,天生的線程安全
缺點: 沒有懶加載,啓動較慢;如果從始至終都沒使用過這個實例,則會造成內存的浪費。
2、餓漢式的變種寫發
package com.app.singlecase;
/**
* @ClassName: HungryManTwo
* @Description 單例模式餓漢式的變種
* @Date:2019/9/29 11:10
* @Author: lk
* @see
* @since JDK 1.8
*/
public class HungryManTwo {
private HungryManTwo(){
}
private static final HungryManTwo HUNGRY_MAN_TWO;
static {
HUNGRY_MAN_TWO=new HungryManTwo();
}
private static HungryManTwo getHungryManTwo(){
return HUNGRY_MAN_TWO;
}
}
優缺點同上
3、懶漢式(線程不安全)
package com.app.singlecase;
/**
* @ClassName: Slacker
* @Description 單例模式懶漢式
* @Date:2019/9/29 11:13
* @Author: lk
* @see
* @since JDK 1.8
*/
public class Slacker {
private Slacker(){
}
private static Slacker slacker;
public static Slacker getSlacker(){
//slacker==null存在競態條件,可能會有多個線程同時進入if語句,產生多個實例
if(slacker==null){
System.out.println("第一次進入");
slacker=new Slacker();
}
return slacker;
}
}
優點: 懶加載,啓動速度快、如果從始至終都沒使用過這個實例,則不會初始化該實力,可節約資源
缺點: 多線程環境下線程不安全。if (singleton == null) 存在競態條件,可能會有多個線程同時進入 if 語句,導致產生多個實例
4、懶漢式的變種(線程安全)
package com.app.singlecase;
/**
* @ClassName: SlackerTwo
* @Description 單例模式懶漢式線程安全
* @Date:2019/9/29 11:18
* @Author: lk
* @see
* @since JDK 1.8
*/
public class SlackerTwo {
private SlackerTwo(){
}
private static SlackerTwo slackerTwo;
public static synchronized SlackerTwo getSlackerTwo(){
if(slackerTwo==null){
slackerTwo=new SlackerTwo();
}
return slackerTwo;
}
}
優點: 解決了上述的安全問題
缺點: 使用synchronized
關鍵字對方法進行加鎖,使得每次只有一個線程進入該方法,在多線程下,效率降低。
5、雙重死鎖
package com.app.singlecase;
/**
* @ClassName: DoubleCheckLock
* @Description 單例模式之雙重檢查鎖
* @Date:2019/9/29 11:24
* @Author: lk
* @see
* @since JDK 1.8
*/
public class DoubleCheckLock {
private DoubleCheckLock(){
}
private static volatile DoubleCheckLock doubleCheckLock;
public static DoubleCheckLock getDoubleCheckLock(){
if(doubleCheckLock==null){
synchronized (DoubleCheckLock.class){
if(doubleCheckLock==null){
doubleCheckLock=new DoubleCheckLock();
}
}
}
return doubleCheckLock;
}
}
優點:線程安全;延遲加載;效率較高。
由於 JVM 具有指令重排的特性,在多線程環境下可能出現 singleton 已經賦值但還沒初始化的情況,導致一個線程獲得還沒有初始化的實例。volatile 關鍵字的作用:
- 保證了不同線程對這個變量進行操作時的可見性
- 禁止進行指令重排序
6、靜態內部類
package com.app.singlecase;
/**
* @ClassName: Inside
* @Description 單例模式之靜態內部類
* @Date:2019/9/29 11:29
* @Author: lk
* @see
* @since JDK 1.8
*/
public class Inside {
private Inside(){
}
//靜態內部類
private static class SingletonInstance {
private static final Inside INSIDE=new Inside();
}
public static Inside getInside(){
return SingletonInstance.INSIDE;
}
}
優點: 避免了線程不安全,延遲加載,效率高。
靜態內部類的方式利用了類裝載機制來保證線程安全,只有在第一次調用getInside方法時,纔會裝載SingletonInstance內部類,完成Singleton的實例化,所以也有懶加載的效果。
7、枚舉(最簡單且線程安全外加能防止序列號攻擊)
package com.app.singlecase;
/**
* @ClassName: SingletonEnum
* @Description 單例模式之枚舉模式
* @Date:2019/9/29 11:32
* @Author: lk
* @see
* @since JDK 1.8
*/
public enum SingletonEnum {
SINGLETON_ENUM;
public void getEnumTest(){
System.out.println("枚舉測試");
}
}
優點: 通過JDK1.5中添加的枚舉來實現單例模式,寫法簡單,且不僅能避免多線程同步問題,而且還能防止反序列化重新創建新的對象。
作者也是看的另外一片博客學習的,下面是那個博客的鏈接
作者學習的博客地址
祝大家面試順利,一起加油。