日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

java单例方法_Java单例模式

發布時間:2024/7/23 java 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java单例方法_Java单例模式 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

單例模式,是特別常見的一種設計模式,因此我們有必要對它的概念和幾種常見的寫法非常了解,而且這也是面試中常問的知識點。

所謂單例模式,就是所有的請求都用一個對象來處理,如我們常用的Spring默認就是單例的,而多例模式是每一次請求都創建一個新的對象來處理,如structs2中的action。

使用單例模式,可以確保一個類只有一個實例,并且易于外部訪問,還可以節省系統資源。如果在系統中,希望某個類的對象只存在一個,就可以使用單例模式。

那怎么確保一個類只有一個實例呢?

我們知道,通常我們會通過new關鍵字來創建一個新的對象。這個時候類的構造函數是public公有的,你可以隨意創建多個類的實例。所以,首先我們需要把構造函數改為private私有的,這樣就不能隨意new對象了,也就控制了多個實例的隨意創建。

然后,定義一個私有的靜態屬性,來代表類的實例,它只能類內部訪問,不允許外部直接訪問。

最后,通過一個靜態的公有方法,把這個私有靜態屬性返回出去,這就為系統創建了一個全局唯一的訪問點。

以上,就是單例模式的三個要素。總結為:私有構造方法

指向自己實例的私有靜態變量

對外的靜態公共訪問方法

單例模式分為餓漢式和懶漢式。它們的主要區別就是,實例化對象的時機不同。餓漢式,是在類加載時就會實例化一個對象。懶漢式,則是在真正使用的時候才會實例化對象。

餓漢式單例代碼實現:

public class Singleton {

// 餓漢式單例,直接創建一個私有的靜態實例

private static Singleton singleton = new Singleton();

//私有構造方法

private Singleton(){

}

//提供一個對外的靜態公有方法

public static Singleton getInstance(){

return singleton;

}

}

懶漢式單例代碼實現

public class Singleton {

// 懶漢式單例,類加載時先不創建實例

private static Singleton singleton = null;

//私有構造方法

private Singleton(){

}

//真正使用時才創建類的實例

public static Singleton getInstance(){

if(singleton == null){

singleton = new Singleton();

}

return singleton;

}

}

稍有經驗的程序員就發現了,以上懶漢式單例的實現方式,在單線程下是沒有問題的。但是,如果在多線程中使用,就會發現它們返回的實例有可能不是同一個。我們可以通過代碼來驗證一下。創建十個線程,分別啟動,線程內去獲得類的實例,把實例的 hashcode 打印出來,只要相同則認為是同一個實例;若不同,則說明創建了多個實例。

public class TestSingleton {

public static void main(String[] args) {

for (int i = 0; i < 10 ; i++) {

new MyThread().start();

}

}

}

class MyThread extends Thread {

@Override

public void run() {

Singleton singleton = Singleton.getInstance();

System.out.println(singleton.hashCode());

}

}

/**

運行多次,就會發現,hashcode會出現不同值

668770925

668770925

649030577

668770925

668770925

668770925

668770925

668770925

668770925

668770925

*/

所以,以上懶漢式的實現方式是線程不安全的。那餓漢式呢?你可以手動測試一下,會發現不管運行多少次,返回的hashcode都是相同的。因此,認為餓漢式單例是線程安全的。

那為什么餓漢式就是線程安全的呢?這是因為,餓漢式單例在類加載時,就創建了類的實例,也就是說在線程去訪問單例對象之前就已經創建好實例了。而一個類在整個生命周期中只會被加載一次。因此,也就可以保證實例只有一個。所以說,餓漢式單例天生就是線程安全的。(可以了解一下類加載機制)

既然懶漢式單例不是線程安全的,那么我們就需要去改造一下,讓它在多線程環境下也能正常工作。以下介紹幾種常見的寫法:

1) 使用synchronized方法

實現非常簡單,只需要在方法上加一個synchronized關鍵字即可

public class Singleton {

private static Singleton singleton = null;

private Singleton(){

}

//使用synchronized修飾方法,即可保證線程安全

public static synchronized Singleton getInstance(){

if(singleton == null){

singleton = new Singleton();

}

return singleton;

}

}

這種方式,雖然可以保證線程安全,但是同步方法的作用域太大,鎖的粒度比較粗,因此,執行效率就比較低。

2) synchronized 同步塊

既然,同步整個方法的作用域大,那我縮小范圍,在方法里邊,只同步創建實例的那一小部分代碼塊不就可以了嗎(因為方法較簡單,所以鎖代碼塊和鎖方法沒什么明顯區別)。

public class Singleton {

private static Singleton singleton = null;

private Singleton(){

}

public static Singleton getInstance(){

//synchronized只修飾方法內部的部分代碼塊

synchronized (Singleton.class){

if(singleton == null){

singleton = new Singleton();

}

}

return singleton;

}

}

這種方法,本質上和第一種沒什么區別,因此,效率提升不大,可以忽略不計。

3) 雙重檢測(double check)

可以看到,以上的第二種方法只要調用getInstance方法,就會走到同步代碼塊里。因此,會對效率產生影響。其實,我們完全可以先判斷實例是否已經存在。若已經存在,則說明已經創建好實例了,也就不需要走同步代碼塊了;若不存在即為空,才進入同步代碼塊,這樣可以提高執行效率。因此,就有以下雙重檢測了:

public class Singleton {

//注意,此變量需要用volatile修飾以防止指令重排序

private static volatile Singleton singleton = null;

private Singleton(){

}

public static Singleton getInstance(){

//進入方法內,先判斷實例是否為空,以確定是否需要進入同步代碼塊

if(singleton == null){

synchronized (Singleton.class){

//進入同步代碼塊時也需要判斷實例是否為空

if(singleton == null){

singleton = new Singleton();

}

}

}

return singleton;

}

}

需要注意的一點是,此方式中,靜態實例變量需要用volatile修飾。因為,new Singleton() 是一個非原子性操作,其流程為:

a.給 singleton 實例分配內存空間

b.調用Singleton類的構造函數創建實例

c.將 singleton 實例指向分配的內存空間,這時認為singleton實例不為空

正常順序為 a->b->c,但是,jvm為了優化編譯程序,有時候會進行指令重排序。就會出現執行順序為 a->c->b。這在多線程中就會表現為,線程1執行了new對象操作,然后發生了指令重排序,會導致singleton實例已經指向了分配的內存空間(c),但是實際上,實例還沒創建完成呢(b)。

這個時候,線程2就會認為實例不為空,判斷 if(singleton == null)為false,于是不走同步代碼塊,直接返回singleton實例(此時拿到的是未實例化的對象),因此,就會導致線程2的對象不可用而使用時報錯。

4)使用靜態內部類

思考一下,由于類加載是按需加載,并且只加載一次,所以能保證線程安全,這也是為什么說餓漢式單例是天生線程安全的。同樣的道理,我們是不是也可以通過定義一個靜態內部類來保證類屬性只被加載一次呢。

public class Singleton {

private Singleton(){

}

//靜態內部類

private static class Holder {

private static Singleton singleton = new Singleton();

}

public static Singleton getInstance(){

//調用內部類的屬性,獲取單例對象

return Holder.singleton;

}

}

而且,JVM在加載外部類的時候,不會加載靜態內部類,只有在內部類的方法或屬性(此處即指singleton實例)被調用時才會加載,因此不會造成空間的浪費。

5)使用枚舉類

因為枚舉類是線程安全的,并且只會加載一次,所以利用這個特性,可以通過枚舉類來實現單例。

public class Singleton {

private Singleton(){

}

//定義一個枚舉類

private enum SingletonEnum {

//創建一個枚舉實例

INSTANCE;

private Singleton singleton;

//在枚舉類的構造方法內實例化單例類

SingletonEnum(){

singleton = new Singleton();

}

private Singleton getInstance(){

return singleton;

}

}

public static Singleton getInstance(){

//獲取singleton實例

return SingletonEnum.INSTANCE.getInstance();

}

}

總結

以上是生活随笔為你收集整理的java单例方法_Java单例模式的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。