我從我的博客里把我的文章粘貼過來吧,對于單例模式模式應(yīng)該有比較清楚的解釋:
成都網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)建站!專注于網(wǎng)頁設(shè)計(jì)、成都網(wǎng)站建設(shè)、微信開發(fā)、成都小程序開發(fā)、集團(tuán)成都企業(yè)網(wǎng)站定制等服務(wù)項(xiàng)目。核心團(tuán)隊(duì)均擁有互聯(lián)網(wǎng)行業(yè)多年經(jīng)驗(yàn),服務(wù)眾多知名企業(yè)客戶;涵蓋的客戶類型包括:廣告設(shè)計(jì)等眾多領(lǐng)域,積累了大量豐富的經(jīng)驗(yàn),同時(shí)也獲得了客戶的一致贊賞!
單例模式在我們?nèi)粘5捻?xiàng)目中十分常見,當(dāng)我們在項(xiàng)目中需要一個(gè)這樣的一個(gè)對象,這個(gè)對象在內(nèi)存中只能有一個(gè)實(shí)例,這時(shí)我們就需要用到單例。
一般說來,單例模式通常有以下幾種:
1.饑漢式單例
public class Singleton {
private Singleton(){};
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
這是最簡單的單例,這種單例最常見,也很可靠!它有個(gè)唯一的缺點(diǎn)就是無法完成延遲加載——即當(dāng)系統(tǒng)還沒有用到此單例時(shí),單例就會(huì)被加載到內(nèi)存中。
在這里我們可以做個(gè)這樣的測試:
將上述代碼修改為:
public class Singleton {
private Singleton(){
System.out.println("createSingleton");
};
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
public static void testSingleton(){
System.out.println("CreateString");
}
}
而我們在另外一個(gè)測試類中對它進(jìn)行測試(本例所有測試都通過Junit進(jìn)行測試)
public class TestSingleton {
@Test
public void test(){
Singleton.testSingleton();
}
}
輸出結(jié)果:
createSingleton
CreateString
我們可以注意到,在這個(gè)單例中,即使我們沒有使用單例類,它還是被創(chuàng)建出來了,這當(dāng)然是我們所不愿意看到的,所以也就有了以下一種單例。
2.懶漢式單例
public class Singleton1 {
private Singleton1(){
System.out.println("createSingleton");
}
private static Singleton1 instance = null;
public static synchronized Singleton1 getInstance(){
return instance==null?new Singleton1():instance;
}
public static void testSingleton(){
System.out.println("CreateString");
}
}
上面的單例獲取實(shí)例時(shí),是需要加上同步的,如果不加上同步,在多線程的環(huán)境中,當(dāng)線程1完成新建單例操作,而在完成賦值操作之前,線程2就可能判
斷instance為空,此時(shí),線程2也將啟動(dòng)新建單例的操作,那么多個(gè)就出現(xiàn)了多個(gè)實(shí)例被新建,也就違反了我們使用單例模式的初衷了。
我們在這里也通過一個(gè)測試類,對它進(jìn)行測試,最后面輸出是
CreateString
可以看出,在未使用到單例類時(shí),單例類并不會(huì)加載到內(nèi)存中,只有我們需要使用到他的時(shí)候,才會(huì)進(jìn)行實(shí)例化。
這種單例解決了單例的延遲加載,但是由于引入了同步的關(guān)鍵字,因此在多線程的環(huán)境下,所需的消耗的時(shí)間要遠(yuǎn)遠(yuǎn)大于第一種單例。我們可以通過一段測試代碼來說明這個(gè)問題。
public class TestSingleton {
@Test
public void test(){
long beginTime1 = System.currentTimeMillis();
for(int i=0;i100000;i++){
Singleton.getInstance();
}
System.out.println("單例1花費(fèi)時(shí)間:"+(System.currentTimeMillis()-beginTime1));
long beginTime2 = System.currentTimeMillis();
for(int i=0;i100000;i++){
Singleton1.getInstance();
}
System.out.println("單例2花費(fèi)時(shí)間:"+(System.currentTimeMillis()-beginTime2));
}
}
最后輸出的是:
單例1花費(fèi)時(shí)間:0
單例2花費(fèi)時(shí)間:10
可以看到,使用第一種單例耗時(shí)0ms,第二種單例耗時(shí)10ms,性能上存在明顯的差異。為了使用延遲加載的功能,而導(dǎo)致單例的性能上存在明顯差異,
是不是會(huì)得不償失呢?是否可以找到一種更好的解決的辦法呢?既可以解決延遲加載,又不至于性能損耗過多,所以,也就有了第三種單例:
3.內(nèi)部類托管單例
public class Singleton2 {
private Singleton2(){}
private static class SingletonHolder{
private static Singleton2 instance=new Singleton2();
}
private static Singleton2 getInstance(){
return SingletonHolder.instance;
}
}
在這個(gè)單例中,我們通過靜態(tài)內(nèi)部類來托管單例,當(dāng)這個(gè)單例被加載時(shí),不會(huì)初始化單例類,只有當(dāng)getInstance方法被調(diào)用的時(shí)候,才會(huì)去加載
SingletonHolder,從而才會(huì)去初始化instance。并且,單例的加載是在內(nèi)部類的加載的時(shí)候完成的,所以天生對線程友好,而且也不需要
synchnoized關(guān)鍵字,可以說是兼具了以上的兩個(gè)優(yōu)點(diǎn)。
4.總結(jié)
一般來說,上述的單例已經(jīng)基本可以保證在一個(gè)系統(tǒng)中只會(huì)存在一個(gè)實(shí)例了,但是,仍然可能會(huì)有其他的情況,導(dǎo)致系統(tǒng)生成多個(gè)單例,請看以下情況:
public class Singleton3 implements Serializable{
private Singleton3(){}
private static class SingletonHolder{
private static Singleton3 instance = new Singleton3();
}
public static Singleton3 getInstance(){
return SingletonHolder.instance;
}
}
通過一段代碼來測試:
@Test
public void test() throws Exception{
Singleton3 s1 = null;
Singleton3 s2 = Singleton3.getInstance();
//1.將實(shí)例串行話到文件
FileOutputStream fos = new FileOutputStream("singleton.txt");
ObjectOutputStream oos =new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
//2.從文件中讀取出單例
FileInputStream fis = new FileInputStream("singleton.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (Singleton3) ois.readObject();
if(s1==s2){
System.out.println("同一個(gè)實(shí)例");
}else{
System.out.println("不是同一個(gè)實(shí)例");
}
}
輸出:
不是同一個(gè)實(shí)例
可以看到當(dāng)我們把單例反序列化后,生成了多個(gè)不同的單例類,此時(shí),我們必須在原來的代碼中加入readResolve()函數(shù),來阻止它生成新的單例
public class Singleton3 implements Serializable{
private Singleton3(){}
private static class SingletonHolder{
private static Singleton3 instance = new Singleton3();
}
public static Singleton3 getInstance(){
return SingletonHolder.instance;
}
//阻止生成新的實(shí)例
public Object readResolve(){
return SingletonHolder.instance;
}
}
再次測試時(shí),就可以發(fā)現(xiàn)他們生成的是同一個(gè)實(shí)例了。
單例模式大致有五種寫法,分別為懶漢,惡漢,靜態(tài)內(nèi)部類,枚舉和雙重校驗(yàn)鎖。
1、懶漢寫法,常用寫法
class?LazySingleton{
private?static?LazySingleton?singleton;
private?LazySingleton(){
}
public?static?LazySingleton?getInstance(){
if(singleton==null){
singleton=new?LazySingleton();
}
return?singleton;
}???
}
2、惡漢寫法,缺點(diǎn)是沒有達(dá)到lazy loading的效果
class?HungrySingleton{
private?static?HungrySingleton?singleton=new?HungrySingleton();
private?HungrySingleton(){}
public?static?HungrySingleton?getInstance(){
return?singleton;
}
}
3、靜態(tài)內(nèi)部類,優(yōu)點(diǎn):加載時(shí)不會(huì)初始化靜態(tài)變量INSTANCE,因?yàn)闆]有主動(dòng)使用,達(dá)到Lazy loading
class?InternalSingleton{
private?static?class?SingletonHolder{
private?final?static??InternalSingleton?INSTANCE=new?InternalSingleton();
}???
private?InternalSingleton(){}
public?static?InternalSingleton?getInstance(){
return?SingletonHolder.INSTANCE;
}
}
4、枚舉,優(yōu)點(diǎn):不僅能避免多線程同步問題,而且還能防止反序列化重新創(chuàng)建新的對象
enum?EnumSingleton{
INSTANCE;
public?void?doSomeThing(){
}
}
5、雙重校驗(yàn)鎖,在當(dāng)前的內(nèi)存模型中無效
class?LockSingleton{
private?volatile?static?LockSingleton?singleton;
private?LockSingleton(){}
//詳見:
public?static?LockSingleton?getInstance(){
if(singleton==null){
synchronized(LockSingleton.class){
if(singleton==null){
singleton=new?LockSingleton();
}
}
}
return?singleton;
}
}
參考自:
你好!
一個(gè)常見情景,單例類在多線程環(huán)境中違反契約。如果你要一個(gè)新手寫出單例模式,可能會(huì)得到下面的代碼:
private static Singleton _instance;
public static Singleton getInstance() {
if (_instance == null) {
_instance = new Singleton();
}
return _instance;
}
然后,當(dāng)你指出這段代碼在超過一個(gè)線程并行被調(diào)用的時(shí)候會(huì)創(chuàng)建多個(gè)實(shí)例的問題時(shí),他很可能會(huì)把整個(gè)getInstance()方法設(shè)為同步(synchronized),就像我們展示的第二段示例代碼getInstanceTS()方法一樣。盡管這樣做到了線程安全,并且解決了多實(shí)例問題,但并不高效。在任何調(diào)用這個(gè)方法的時(shí)候,你都需要承受同步帶來的性能開銷,然而同步只在第一次調(diào)用的時(shí)候才被需要,也就是單例類實(shí)例創(chuàng)建的時(shí)候。這將促使我們使用雙重檢查鎖模式(double checked locking pattern),一種只在臨界區(qū)代碼加鎖的方法。程序員稱其為雙重檢查鎖,因?yàn)闀?huì)有兩次檢查 _instance == null,一次不加鎖,另一次在同步塊上加鎖。這就是使用Java雙重檢查鎖的示例:
public static Singleton getInstanceDC() {
if (_instance == null) { // Single Checked
synchronized (Singleton.class) {
if (_instance == null) { // Double checked
_instance = new Singleton();
}
}
}
return _instance;
}
這個(gè)方法表面上看起來很完美,你只需要付出一次同步塊的開銷,但它依然有問題。除非你聲明_instance變量時(shí)使用了volatile關(guān)鍵字。沒有volatile修飾符,可能出現(xiàn)Java中的另一個(gè)線程看到個(gè)初始化了一半的_instance的情況,但使用了volatile變量后,就能保證先行發(fā)生關(guān)系(happens-before relationship)。對于volatile變量_instance,所有的寫(write)都將先行發(fā)生于讀(read),在Java 5之前不是這樣,所以在這之前使用雙重檢查鎖有問題?,F(xiàn)在,有了先行發(fā)生的保障(happens-before guarantee),你可以安全地假設(shè)其會(huì)工作良好。另外,這不是創(chuàng)建線程安全的單例模式的最好方法,你可以使用枚舉實(shí)現(xiàn)單例模式,這種方法在實(shí)例創(chuàng)建時(shí)提供了內(nèi)置的線程安全。另一種方法是使用靜態(tài)持有者模式(static holder pattern)。
/*
* A journey to write double checked locking of Singleton class in Java.
*/
class Singleton {
private volatile static Singleton _instance;
private Singleton() {
// preventing Singleton object instantiation from outside
}
/*
* 1st version: creates multiple instance if two thread access
* this method simultaneously
*/
public static Singleton getInstance() {
if (_instance == null) {
_instance = new Singleton();
}
return _instance;
}
/*
* 2nd version : this definitely thread-safe and only
* creates one instance of Singleton on concurrent environment
* but unnecessarily expensive due to cost of synchronization
* at every call.
*/
public static synchronized Singleton getInstanceTS() {
if (_instance == null) {
_instance = new Singleton();
}
return _instance;
}
/*
* 3rd version : An implementation of double checked locking of Singleton.
* Intention is to minimize cost of synchronization and improve performance,
* by only locking critical section of code, the code which creates instance of Singleton class.
* By the way this is still broken, if we don't make _instance volatile, as another thread can
* see a half initialized instance of Singleton.
*/
public static Singleton getInstanceDC() {
if (_instance == null) {
synchronized (Singleton.class) {
if (_instance == null) {
_instance = new Singleton();
}
}
}
return _instance;
}
}
這就是本文的所有內(nèi)容了。這是個(gè)用Java創(chuàng)建線程安全單例模式的有爭議的方法,使用枚舉實(shí)現(xiàn)單例類更簡單有效。我并不建議你像這樣實(shí)現(xiàn)單例模式,因?yàn)橛肑ava有許多更好的方式。但是,這個(gè)問題有歷史意義,也教授了并發(fā)是如何引入一些微妙錯(cuò)誤的。正如之前所說,這是面試中非常重要的一點(diǎn)。
在去參加任何Java面試之前,要練習(xí)手寫雙重檢查鎖實(shí)現(xiàn)單例類。這將增強(qiáng)你發(fā)現(xiàn)Java程序員們所犯編碼錯(cuò)誤的洞察力。另外,在現(xiàn)在的測試驅(qū)動(dòng)開發(fā)中,單例模式由于難以被模擬其行為而被視為反模式(anti pattern),所以如果你是測試驅(qū)動(dòng)開發(fā)的開發(fā)者,最好避免使用單例模式。轉(zhuǎn)載,僅供參考。
這個(gè)模式保護(hù)類的創(chuàng)建過程來確保只有一個(gè)實(shí)例被創(chuàng)建,它通過設(shè)置類的構(gòu)造方法為私有來達(dá)到這個(gè)目的。 要獲得類的實(shí)例,單例類可以提供一個(gè)方法,如getInstance,來返回類的實(shí)例。該方法是唯一可以訪問類來創(chuàng)建實(shí)例的方法。 下面是單例的一個(gè)例子:Java代碼publicclassSingletonPattern{privatestaticSingletonPatterninstance;privateSingletonPattern(){}publicstatic synchronized SingletonPatterngetInstance(){if(instance==null){instance=newSingletonPattern();}returninstance;}} 當(dāng)我們要實(shí)現(xiàn)單例的時(shí)候,有如下的規(guī)則需要遵循: 從上面的示例代碼中可以看出,一個(gè)單例類有一個(gè)靜態(tài)的屬性來保存它唯一的實(shí)例 需要將類的構(gòu)造方法設(shè)置為private。這樣你不允許其他任何類來創(chuàng)建單例類的實(shí)例,因?yàn)樗鼈儾荒茉L問單例類的構(gòu)造方法。
單例模式:就是一個(gè)類僅創(chuàng)建一個(gè)對象;
public?class?Singleton?{
private?static?volatile?Singleton?singleton?=?null;
private?Singleton(){}//?構(gòu)造方法
public?static?Singleton?getSingleton(){//?單例模式
if(singleton?==?null){
synchronized?(Singleton.class){
if(singleton?==?null){
singleton?=?new?Singleton();
}
}
}
return?singleton;
}????
}
JAVA
單例模式的幾種實(shí)現(xiàn)方法
1.餓漢式單例類
package
pattern.singleton;
//
餓漢式單例類
.
在類初始化時(shí),已經(jīng)自行實(shí)例化
public
class
Singleton1
{
//
私有的默認(rèn)構(gòu)造子
private
Singleton1()
{}
//
已經(jīng)自行實(shí)例化
private
static
final
Singleton1
single
=
new
Singleton1();
//
靜態(tài)工廠方法
public
static
Singleton1
getInstance()
{
return
single;
}
}
2.
懶漢式單例類
package
pattern.singleton;
//
懶漢式單例類
.
在第一次調(diào)用的時(shí)候?qū)嵗?/p>
public
class
Singleton2
{
//
私有的默認(rèn)構(gòu)造子
private
Singleton2()
{}
//
注意,這里沒有
final
private
static
Singleton2
single;
//
只實(shí)例化一次
static
{
single
=
new
Singleton2();
}
//
靜態(tài)工廠方法
public
synchronized
static
Singleton2
getInstance()
{
if
(single
==
null
)
{
single
=
new
Singleton2();
}
return
single;
}
}
在上面給出懶漢式單例類實(shí)現(xiàn)里對靜態(tài)工廠方法使用了同步化,以處理多線程環(huán)境。有些設(shè)計(jì)師在這里建議使用所謂的
"
雙重檢查成例
".
必須指出的是,
"
雙重檢查成例
"
不可以在
Java
語言中使用。不十分熟悉的讀者,可以看看后面給出的小節(jié)。
同
樣,由于構(gòu)造子是私有的,因此,此類不能被繼承。餓漢式單例類在自己被加載時(shí)就將自己實(shí)例化。即便加載器是靜態(tài)的,在餓漢
式單例類被加載時(shí)仍會(huì)將自己實(shí)例化。單從資源利用效率角度來講,這個(gè)比懶漢式單例類稍差些。從速度和反應(yīng)時(shí)間角度來講,
則
比懶漢式單例類稍好些。然而,懶漢式單例類在實(shí)例化時(shí),必須處
理好在多個(gè)線程同時(shí)首次引用此類時(shí)的訪問限制問題,特別是當(dāng)單例類作為資源控制器,在實(shí)例化時(shí)必然涉及資源初始化,而資源
初始化很有可能耗費(fèi)時(shí)間。這意味著出現(xiàn)多線程同時(shí)首次引用此類的機(jī)率變得較大。
餓漢式單例類可以在
Java
語言內(nèi)實(shí)現(xiàn),
但不易在
C++
內(nèi)實(shí)現(xiàn),因?yàn)殪o態(tài)初始化在
C++
里沒有固定的順序,因而靜態(tài)的
m_instance
變量的初始化與類的加載順序沒有保證,可能會(huì)出問題。這就是為什么
GoF
在提出單例類的概念時(shí),舉的例子是懶
漢式的。他們的書影響之大,以致
Java
語言中單例類的例子也大多是懶漢式的。實(shí)際上,本書認(rèn)為餓漢式單例類更符合
Java
語
言本身的特點(diǎn)。
3.
登記式單例類
.
package
pattern.singleton;
import
java.util.HashMap;
import
java.util.Map;
//
登記式單例類
.
//
類似
Spring
里面的方法,將類名注冊,下次從里面直接獲取。
public
class
Singleton3
{
private
static
MapString,Singleton3
map
=
new
HashMapString,Singleton3();
static
{
Singleton3
single
=
new
Singleton3();
map.put(single.getClass().getName(),
single);
}
//
保護(hù)的默認(rèn)構(gòu)造子
protected
Singleton3(){}
//
靜態(tài)工廠方法
,
返還此類惟一的實(shí)例
public
static
Singleton3
getInstance(String
name)
{
if
(name
==
null
)
{
name
=
Singleton3.
class
.getName();
System.out.println("name
==
null"+"---name="+name);
}
if
(map.get(name)
==
null
)
{
try
{
map.put(name,
(Singleton3)
Class.forName(name).newInstance());
}
catch
(InstantiationException
e)
{
e.printStackTrace();
}
catch
(IllegalAccessException
e)
{
e.printStackTrace();
}
catch
(ClassNotFoundException
e)
{
e.printStackTrace();
}
}
return
map.get(name);
}
//
一個(gè)示意性的商業(yè)方法
public
String
about()
{
return
"Hello,
I
am
RegSingleton.";
}
public
static
void
main(String[]
args)
{
Singleton3
single3
=
Singleton3.getInstance(
null
);
System.out.println(single3.about());
}
}
本文名稱:java代碼實(shí)現(xiàn)單例,java單例模式例子
瀏覽路徑:http://chinadenli.net/article42/hddiec.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供App開發(fā)、建站公司、網(wǎng)站收錄、App設(shè)計(jì)、網(wǎng)站設(shè)計(jì)、移動(dòng)網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)