介紹
單例模式(Singleton Pattern)是 Java 中最簡單的設(shè)計模式之一回怜。這種類型的設(shè)計模式屬于創(chuàng)建型模式仓洼,它提供了一種創(chuàng)建對象的最佳方式公壤。
這種模式涉及到一個單一的類,該類負(fù)責(zé)創(chuàng)建自己的對象商模,同時確保只有單個對象被創(chuàng)建奠旺。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問施流,不需要實例化該類的對象响疚。
注意:
1、單例類只能有一個實例瞪醋。
2忿晕、單例類必須自己創(chuàng)建自己的唯一實例。
3趟章、單例類必須給所有其他對象提供這一實例杏糙。
實現(xiàn)方式
餓漢式單例(靜態(tài)常量慎王,線程安全)
顧名思義,餓漢式單例它很“餓”宏侍,所以一開始就創(chuàng)建了唯一但單例實例赖淤,但如果你沒有使用過這個實例,就會造成內(nèi)存的浪費
/**
* 餓漢式單例
* 優(yōu)點:簡單谅河,在類裝載時就完成了實例化咱旱,避免了線程同步問題,線程安全
* 缺點:由于這個類已經(jīng)完成了實例化绷耍,如果從始至終都沒有用過這個實例吐限,就會造成內(nèi)存的浪費
*/
public class SingletonTest01 {
public static void main(String[] args) {
Signleton instance1= Signleton.getInstance();
Signleton instance2 = Signleton.getInstance();
System.out.println(instance1==instance2);
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
class Signleton{
//1、構(gòu)造器私有化,外部無法通過new新建
private Signleton(){ }
//2褂始、內(nèi)部創(chuàng)建對象實例
private final static Signleton instance = new Signleton();
//3诸典、提供一個公有的靜態(tài)方法,返回實例對象
public final static Signleton getInstance(){
return instance;
}
}
輸出結(jié)果
true
1163157884
1163157884
可以看到輸出的是同一個實例
餓漢式單例(靜態(tài)代碼塊崎苗,線程安全)
和之前的方式類似狐粱,只不過將類實例化的過程放在了靜態(tài)代碼塊中,也就是類裝載的時候胆数,
就執(zhí)行靜態(tài)代碼塊中的代碼肌蜻,優(yōu)缺點和之前一樣
/**
* 和之前的方式類似,只不過將類實例化的過程放在了靜態(tài)代碼塊中必尼,也就是類裝載的時候蒋搜,
* 就執(zhí)行靜態(tài)代碼塊中的代碼,優(yōu)缺點和之前一樣
*/
public class SingletonTest02 extends Thread{
public static void main(String[] args) {
Signleton instance1= Signleton.getInstance();
Signleton instance2 = Signleton.getInstance();
System.out.println(instance1==instance2);
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
class Signleton{
//1判莉、構(gòu)造器私有化,外部無法通過new新建
private Signleton(){}
//2豆挽、內(nèi)部創(chuàng)建對象實例
private static Signleton instance;
static {//靜態(tài)代碼塊種,創(chuàng)建單例對象
instance = new Signleton();
}
//3券盅、提供一個公有的靜態(tài)方法祷杈,返回實例對象
public final static Signleton getInstance(){
return instance;
}
}
輸出
true
1163157884
1163157884
懶漢式(線程不安全)
同樣,顧名思義渗饮,懶漢式單例它很懶。只有在你用到它時宿刮,它才會創(chuàng)建一個實例互站。
/**
* 餓漢式-線程不安全
* 優(yōu)點:起到了懶加載的效果,但是只能在單線程下使用
* 如果在多線程下僵缺,如果一個線程進(jìn)入了if判斷語句塊胡桃,
* 還沒來得及向下執(zhí)行,另一個線程也進(jìn)入這個判斷語句磕潮,就會產(chǎn)生多個實例(違背單例模式)翠胰,
* 實際開發(fā)中容贝,不要使用這種方式
*/
public class SingletonTest03 {
public static void main(String[] args) {
for (int i = 0; i <10 ; i++) {
new Thread(() -> System.out.println(Signleton.getInstance().hashCode()) ).start();
}
}
}
class Signleton{
private static Signleton instance;
private Signleton(){}
//提供一個靜態(tài)的公有方法,當(dāng)調(diào)用方法時之景,才去創(chuàng)建instance
public static Signleton getInstance(){
if(instance == null){//如果為空再去創(chuàng)建對象
instance = new Signleton();
}
return instance;
}
}
輸出
546405844
135417039
135417039
802181073
135417039
135417039
135417039
802181073
135417039
135417039
這里我選了個比較極端的情況斤富,如果你的電腦配置比較好,可能運行幾次結(jié)果都是符合單例模式的锻狗。
懶漢式(同步方法满力,線程安全)
上面方法之所以會存在線程不安全的情況,是因為多線程情況下轻纪,可能會有多條線程同時判斷單例是否創(chuàng)建油额。那么要解決這個問題 ,只需要同步getInstance()方法
/**
* 解決了線程不安全的問題
* 但是大大降低了效率 每個線程想獲得實例的時候刻帚,執(zhí)行g(shù)etInstance()方法都要進(jìn)行同步
*/
public class SingletonTest04 {
public static void main(String[] args) {
for (int i = 0; i <10 ; i++) {
new Thread(() -> System.out.println(Signleton.getInstance().hashCode()) ).start();
}
}
}
class Signleton{
private static Signleton instance;
private Signleton(){}
//提供一個靜態(tài)的公有方法潦嘶,當(dāng)調(diào)用方法時,才去創(chuàng)建instance
public static synchronized Signleton getInstance(){
if(instance == null){//如果為空再去創(chuàng)建對象
instance = new Signleton();
}
return instance;
}
}
結(jié)果
802181073
802181073
802181073
802181073
802181073
802181073
802181073
802181073
802181073
802181073
但是崇众,synchronized是一個很重量的同步鎖掂僵,而我們每次執(zhí)行g(shù)etInstance()時都會進(jìn)行同步,極其影響效率
懶漢式(雙重檢查校摩,線程安全)
雙檢鎖看峻,又叫雙重校驗鎖,綜合了懶漢式和餓漢式兩者的優(yōu)缺點整合而成衙吩』ゼ耍看上面代碼實現(xiàn)中,特點是在synchronized關(guān)鍵字內(nèi)外都加了一層 if 條件判斷坤塞,這樣既保證了線程安全冯勉,又比直接上鎖提高了執(zhí)行效率,還節(jié)省了內(nèi)存空間
/**
* 懶漢模式-雙重檢查
* 進(jìn)行了兩次if判斷檢查摹芙,這樣就保證線程安全了
* 通過判斷是否為空灼狰,來確定是否 需要再次實例化
*/
public class SingletonTest05 {
public static void main(String[] args) {
for (int i = 0; i <10 ; i++) {
new Thread(() -> System.out.println(Signleton.getInstance().hashCode()) ).start();
}
}
}
class Signleton{
private static volatile Signleton instance;//volatile保證可見性
private Signleton(){}
//提供一個靜態(tài)的公有方法,加入雙重檢查代碼浮禾,解決線程安全問題交胚,同時解決懶加載問題
public static Signleton getInstance() {
if (instance == null) {
synchronized (Signleton.class) {
if (instance == null) {
instance = new Signleton();
}
}
}
return instance;
}
}
運行結(jié)果
79372097
79372097
79372097
79372097
79372097
79372097
79372097
79372097
79372097
79372097
推薦使用
靜態(tài)內(nèi)部類(線程安全)
/**
* 靜態(tài)內(nèi)部類實現(xiàn)單例模式
* 該方法采用了類裝載機(jī)制來保證初始化實例時只有一個線程
* 靜態(tài)內(nèi)部類在Signleton類被裝載時并不會立即實例化,而是需要實例化時盈电,才會裝載SignletonInstance類
* 類的靜態(tài)屬性只會在第一次加載類的時候初始化
* 避免了線程不安全蝴簇,利用靜態(tài)內(nèi)部類實現(xiàn)懶加載,效率高
*/
public class SingletonTest07 {
public static void main(String[] args) {
for (int i = 0; i <10 ; i++) {
new Thread(() -> System.out.println(Signleton.getInstance().hashCode()) ).start();
}
}
}
class Signleton{
//構(gòu)造器私有
private Signleton(){}
//靜態(tài)內(nèi)部類匆帚,該類中有一個靜態(tài)屬性Signleton
private static class SignletonInstance{
private static final Signleton instance = new Signleton();
}
//提供一個靜態(tài)的公有方法熬词,直接返回SignletonInstance.instance
public static Signleton getInstance() {
return SignletonInstance.instance;
}
}
結(jié)果
79372097
79372097
79372097
79372097
79372097
79372097
79372097
79372097
79372097
79372097
這種方式較為簡單,推薦使用
枚舉(線程安全)
/**
* @author codermy
* @createTime 2020/5/14
* 枚舉方法實現(xiàn)單例模式
* 借助jdk1.5中添加的枚舉類來實現(xiàn)單例模式,
* 不僅能避免多線程同步問題互拾,而且還能防止反序列化重新創(chuàng)建新對象
*/
public class SingletonTest08 {
public static void main(String[] args) {
Singleton singleton = Singleton.INSTANCE;
singleton.Ok();
for (int i = 0; i <10 ; i++) {
new Thread(() -> System.out.println(Singleton.INSTANCE.hashCode()) ).start();
}
}
}
enum Singleton{
INSTANCE;//屬性
public void Ok(){
System.out.println("ok");
}
}
結(jié)果
ok
858497792
858497792
858497792
858497792
858497792
858497792
858497792
858497792
858497792
858497792
可以看出歪今,枚舉實現(xiàn)單例模式,最為簡潔颜矿,較為推薦寄猩。但是正是因為它簡潔,導(dǎo)致可讀性較差