單例設計模式分爲:八種
- 靜態常量餓漢式
- 靜態代碼塊餓漢式
- 線程不安全懶漢式
- 線程安全懶漢式
- 同步代碼塊懶漢式
- DoubleCheck(雙重鎖)
- 靜態內部類
- 枚舉方式ENUM
/**
* @ClassName SingleTest01
* @Description 單列模式-餓漢式-靜態常量
* @Author Chao.Qin
* @Datw 2019/11/28 10:46
*/
public class SingleTest01 {
private SingleTest01(){
}
private static final SingleTest01 singleTest = new SingleTest01();
public static SingleTest01 getInstance(){
return singleTest;
}
}
class SinTest01{
public static void main(String[] args) {
SingleTest01 singleTest01 = SingleTest01.getInstance();
SingleTest01 singleTest02 = SingleTest01.getInstance();
System.out.println(singleTest01==singleTest02);
}
}
- 優點:寫法簡單,在類裝載的時候完成實列化。避免了線程同步問題
- 缺點:在類裝載的時候就完成實例化,沒有達到Lazy Loading(懶加載)的效果,如果從始至終沒有使用該類,就會造成內存浪費
- 這種方式基於classloder機制避免的線程同步問題,不過,instance在類裝載時就進行實例化,在單例模式中大多數都調用了getInstance方法,但導致類裝載的原因有很多種,因此不能確定有其他方式(或者其他靜態方法)導致類裝載,這時候初始化instance就沒有達到懶加載的效果
- 結論:此單例方法可用,可能會造成內存浪費
/**
* @ClassName SingleTest02
* @Description 單列模式-餓漢式-靜態代碼塊
* @Author Chao.Qin
* @Datw 2019/11/28 10:50
*/
public class SingleTest02 {
private SingleTest02(){
}
private static SingleTest02 instance;
static {
instance = new SingleTest02();
}
public static SingleTest02 getInstance(){
return instance;
}
}
class SinTest02{
public static void main(String[] args) {
SingleTest02 singleTest01 = SingleTest02.getInstance();
SingleTest02 singleTest02 = SingleTest02.getInstance();
System.out.println(singleTest01==singleTest02);
}
}
- 這種方式和靜態常量餓漢式類似,只不過將類實例化的過程放在了靜態代碼塊中,也是在類裝載的時候就執行了靜態代碼塊中的代碼,初始化實例,和靜態常量餓漢式的優缺點一樣
- 結論:此單例方法可用,可能會造成內存浪費
/**
* @ClassName SingleTest03
* @Description 線程不安全懶漢式
* @Author Chao.Qin
* @Datw 2019/12/12 14:29
*/
public class SingleTest03 {
private static SingleTest03 instance;
private SingleTest03(){}
//提供靜態的公有方法,當使用到該方式時,纔去創建instance-----即懶漢式
public static SingleTest03 getInstance(){
if (instance == null){
instance = new SingleTest03();
}
return instance;
}
}
class SinTest03{
public static void main(String[] args) {
SingleTest03 instance = SingleTest03.getInstance();
SingleTest03 instance1 = SingleTest03.getInstance();
System.out.println(instance==instance1);
}
}
- 起到了Lazy Loading(懶加載)的效果,但只能在單線程下使用
- 如果在多線程下,一個線程進入if(instance==null)判斷語句塊,還未來的及往下執行,另一個線程也通過了該判斷語句,這時就會產生多個實例,所以在多線程環境下不可以使用該方式
- 結論:在實際開發中,不推薦使用該方法
/**
* @ClassName SingleTest04
* @Description 線程安全懶漢式
* @Author Chao.Qin
* @Datw 2019/12/12 15:08
*/
public class SingleTest04 {
private static SingleTest04 instance;
private SingleTest04(){}
// 提供靜態的公有方法,加入同步處理,解決線程安全問題
public static synchronized SingleTest04 getInstance(){
if (instance == null){
instance = new SingleTest04();
}
return instance;
}
}
class SinTest04{
public static void main(String[] args) {
SingleTest04 instance = SingleTest04.getInstance();
SingleTest04 instance1 = SingleTest04.getInstance();
System.out.println(instance==instance1);
}
}
- 解決了線程不安全問題
- 效率太低了,每個線程想獲取實例化對象時,執行getInstance()方法都要進行同步,而其實這種方式只需要執行一次實例化即可,後面想獲取實例化直接return就行。該方法同步效率太低
- 結論:在實際開發中,不推薦使用該方法
/**
* @ClassName SingleTest05
* @Description 同步代碼塊懶漢式
* @Author Chao.Qin
* @Datw 2019/12/12 15:08
*/
public class SingleTest05 {
private static SingleTest05 instance;
private SingleTest05() {
}
public static SingleTest05 getInstance() {
if (instance == null) {
synchronized (SingleTest05.class) {
instance = new SingleTest05();
}
}
return instance;
}
}
class SinTest05 {
public static void main(String[] args) {
SingleTest05instance = SingleTest05.getInstance();
SingleTest05instance1 = SingleTest05.getInstance();
System.out.println(instance == instance1);
}
}
- 該種方式:對線程安全懶漢式進行改造,因爲前一種方式效率低,改成同步代碼塊的形式
- 但這種方式並不能起到線程同步的作用,跟線程不安全懶漢式實際方式一致,如果一個線程進入if(instance==null)判斷語句塊,還未來的及往下執行,另一個線程也通過了該判斷語句,這時就會產生多個實例,所以在多線程環境下不可以使用該方式
- 結論:在實際開發中,不推薦使用該方法
/**
* @ClassName SingleTest06
* @Description 雙重檢查
* @Author Chao.Qin
* @Datw 2019/12/12 15:08
*/
public class SingleTest06 {
private static volatile SingleTest06 instance;
private SingleTest06() {
}
public static SingleTest06 getInstance() {
if (instance == null) {
synchronized (SingleTest06.class) {
if (instance == null) {
instance = new SingleTest06();
}
}
}
return instance;
}
}
class SinTest06 {
public static void main(String[] args) {
SingleTest06 instance = SingleTest06.getInstance();
SingleTest06 instance1 = SingleTest06.getInstance();
System.out.println(instance == instance1);
}
}
- DoubleCheck(雙重鎖)雙重檢查優缺點說明
- DoubleCheck是多線程開發中使用的,如代碼所示,進行兩次判斷if(instance==null)檢查,這樣既可以保證線程安全,又可以保證對象實例化一次
- 實例化代碼只會執行一次,後面再訪問時,判斷語句就會直接return實例化對象,避免了多次創建實例化對像,反覆進行方法同步
- 線程安全,延遲加載,效率高
- 結論:推薦再實際開發中使用
- 這種方式採用了類裝載的機制來保證初始化實例只有一個線程
- 靜態內部類在類裝載時並不會立即實例化,而是在需要實例化時,調用SingleInstance方法纔會裝載該類,從而完成SingleTest7的實例化
- 類的靜態屬性只會在第一次加載的時候進行初始化,所以在這裏,JVM保證了線程安全性,在類進行初始化時,只會有一個線程進行,別的線程無法進入
- 優點:避免了線程不安全,利用了靜態內部類的特點實現了延遲加載,效率高
- 結論:推薦使用
enum Singletest8{
INSTANCE;
public void method(){
}
}
- JDK1.5添加枚舉類,實現單例模式,不僅避免了多線程同步問題,從而還能防止反序列化重新創建新的對象
- 這種方式是Effective Java 作者JSON Bloch 提倡的方式
- 結論:推薦使用
- 單例模式保證了 系統內存中該類只存在一個對象,節省系統資源,對於一些頻繁創建銷燬的對象,使用單例可以提高系統的性能
- 當想實例化一個單例類的時候,必須要記住使用相應的獲取對象的方法,而不是使用new
- 單例模式使用的場景:需要頻繁創建和銷燬的對象,創建對象時耗時過多和耗費資源過多(即:重量級對象),但又經常使用的對象,工具類對象,頻繁訪問數據庫或文件對象(比如:數據源,session工廠等)