java單例模式實現(xiàn)的方法
1.最基本的單例模式
/**
* @author LearnAndGet
* @time 2018年11月13日
* 最基本的單例模式 */public class SingletonV1 {
private static SingletonV1 instance = new SingletonV1();;
//構造函數(shù)私有化
private SingletonV1() {} public static SingletonV1 getInstance()
{ return instance;
}
}
import org.junit.Test;public class SingletonTest {
@Test public void test01() throws Exception
{
SingletonV1 s1 = SingletonV1.getInstance();
SingletonV1 s2 = SingletonV1.getInstance();
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
}
}//運行結果如下:589873731
589873731
2.類加載時不初始化實例的模式
上述單例模式在類加載的時候,就會生成實例,可能造成空間浪費,如果需要修改成,在需要使用時才生成實例,則可修改代碼如下:
public class SingletonV2
{ private static SingletonV2 instance; //構造函數(shù)私有化
private SingletonV2() {}
public static SingletonV2 getInstance()
{ if(instance == null)
{ instance = new SingletonV2();
}
return instance; }
}
然而,上述方案雖然在類加載時不會生成實例,但是存在線程安全問題,如果線程A在執(zhí)行到第10行時,線程B也進入該代碼塊,恰好也執(zhí)行好第10行,此時如果實例尚未生成,則線程A和線程B都會執(zhí)行第12行的代碼,各自生成一個實例,此時就違背了單例模式的設計原則。實際測試代碼如下:
public class SingletonTest {
@Test public void test02() throws Exception
{
for(int i=0;i<1000;i++)
{
Thread th1 = new getInstanceThread();
th1.start();
}
}
class getInstanceThread extends Thread
{ public void run()
{ try
{
SingletonV2 s = SingletonV2.getInstance();
System.out.println(Thread.currentThread().getName()+" get Instance "+s.hashCode()+" Time: "+System.currentTimeMillis());
}catch(Exception e)
{
e.printStackTrace();
}
}
}
}
經過多次測試,可能產生如下輸出結果:

3.線程安全的單例模式
在上述單例模式下進行改進,在getInstance方法前加入 Sychronized關鍵字,來實現(xiàn)線程安全,修改后代碼如下:
public class SingletonV3 {
private static SingletonV3 instance;
//構造函數(shù)私有化
private SingletonV3() {}
//synchronized關鍵字在靜態(tài)方法上,鎖定的是當前類:
public static synchronized SingletonV3 getInstance()
{
if(instance == null)
{
instance = new SingletonV3();
}
return instance;
}
}
增加sychronized關鍵字后,確實能夠改善線程安全問題,但是也帶來了額外的鎖開銷。性能受到一定影響。舉例來說,此時如果有1000個線程都需要使用SingletonV3實例,因為加鎖的位置在getInstance上,因此,每個線程都必須等待其他獲取了鎖的線程完全執(zhí)行完鎖中的方法后,才能夠進入該方法并獲取自己的實例。
4.雙重校檢+線程安全單例模式
于是可以在上述代碼的基礎上,只有當Singleton實例未被初始化時,對實例化方法加鎖即可。在Singleton實例已經被初始化時,無需加鎖,直接返回當前Singleton對象。代碼如下:
private static SingletonV4 instance;
//構造函數(shù)私有化
private SingletonV4() {}
public static SingletonV4 getInstance()
{
if(instance == null)
{
synchronized(SingletonV4.class)
{
//雙重校檢
if(instance == null)
{
instance = new SingletonV4();
}
}
}
return instance;
}
5.內部類單例模式
盡管上述方案解決了同步問題,雙重校檢也使得性能開銷大大減小,但是,只有有synchronized關鍵字的存在。性能多多少少還是會有一些影響,此時,我們想到了 "內部類"的用法。
①.內部類不會隨著類的加載而加載
?、?一個類被加載,當且僅當其某個靜態(tài)成員(靜態(tài)域、構造器、靜態(tài)方法等)被調用時發(fā)生。
靜態(tài)內部類隨著方法調用而被加載,只加載一次,不存在并發(fā)問題,所以是線程安全?;诖?,修改代碼如下:
public class SingletonV5 {
//構造函數(shù)私有化
private SingletonV5() {}
static class SingetonGet
{
private static final SingletonV5 instance = new SingletonV5();
}
public static SingletonV5 getInstance()
{
return SingetonGet.instance;
}
}
6.反射都不能破壞的單例模式
靜態(tài)內部類實現(xiàn)的單例模式,是目前比較推薦的方式,但是在java功能強大反射的機制下,它就是個弟弟,此時利用反射仍然能夠創(chuàng)建出多個實例,以下是創(chuàng)建實例的代碼:
@Test
public void test4()
{
//普通方式獲取實例s1,s2
SingletonV5 s1 = SingletonV5.getInstance();
SingletonV5 s2 = SingletonV5.getInstance();
//利用反射獲取實例s3,s4
SingletonV5 s3 = null;
SingletonV5 s4 = null;
try
{
Class<SingletonV5> clazz = SingletonV5.class;
Constructor<SingletonV5> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
s3 = constructor.newInstance();
s4 = constructor.newInstance();
}catch(Exception e)
{
e.printStackTrace();
}
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s3.hashCode());
System.out.println(s4.hashCode());
}
輸出結果如下:
589873731
589873731
200006406
2052001577
可以看到,s1和s2擁有相同的哈希碼,因此他們是同一個實例,但是s3、s4,是通過反射后用構造函數(shù)重新構造生成的實例,他們均與s1,s2不同。此時單例模式下產生了多個不同的對象,違反了設計原則。
基于上述反射可能造成的單例模式失效,考慮在私有的構造函數(shù)中添加是否初始化的標記位,使私有構造方法只可能被執(zhí)行一次。
public class SingletonV6 { //是否已經初始化過的標記位
private static boolean isInitialized = false;
//構造函數(shù)中,當實例已經被初始化時,不能繼續(xù)獲取新實例
private SingletonV6()
{ synchronized(SingletonV6.class)
{ if(isInitialized == false)
{
isInitialized = !isInitialized;
}else
{ throw new RuntimeException("單例模式被破壞...");
}
}
} static class SingetonGet
{ private static final SingletonV6 instance = new SingletonV6();
}
public static SingletonV6 getInstance()
{ return SingetonGet.instance;
}
}
測試代碼如下:
@Test public void test5()
{
SingletonV6 s1 = SingletonV6.getInstance();
SingletonV6 s2 = null; try
{
Class<SingletonV6> clazz = SingletonV6.class;
Constructor<SingletonV6> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
s2 = constructor.newInstance();
}catch(Exception e)
{
e.printStackTrace();
}
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
}
運行上述代碼時,會拋出異常:
java.lang.reflect.InvocationTargetException at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source) at java.lang.reflect.Constructor.newInstance(Unknown Source) at SingletonTest.SingletonTest.test5(SingletonTest.java:98) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206) Caused by: java.lang.RuntimeException: 單例模式被破壞... at SingletonTest.SingletonV6.<init>(SingletonV6.java:26) ... 28 more2052001577
7.序列化反序列化都不能破壞的單例模式
經過上述改進,反射也不能夠破壞單例模式了。但是,依然存在一種可能造成上述單例模式產生兩個不同的實例,那就是序列化。當一個對象A經過序列化,然后再反序列化,獲取到的對象B和A是否是同一個實例呢,驗證代碼如下:
/**
* @Author {LearnAndGet}
* @Time 2018年11月13日
* @Discription:測試序列化并反序列化是否還是同一對象 */package SingletonTest;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInput;import java.io.ObjectInputStream;import java.io.ObjectOutput;import java.io.ObjectOutputStream;public class Main { /**
* @param args */
public static void main(String[] args) { // TODO Auto-generated method stub
SingletonV6 s1 = SingletonV6.getInstance();
ObjectOutput objOut = null;
try { //將s1序列化(記得將Singleton實現(xiàn)Serializable接口)
objOut = new ObjectOutputStream(new FileOutputStream("c:\\a.objFile"));
objOut.writeObject(s1);
objOut.close();
//反序列化得到s2
ObjectInput objIn = new ObjectInputStream(new FileInputStream("c:\\a.objFile"));
SingletonV6 s2 = (SingletonV6) objIn.readObject();
objIn.close();
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
} catch (Exception e)
{ // TODO Auto-generated catch block e.printStackTrace();
}
}
}
輸出結果如下:
1118140819
990368553
可見,此時序列化前的對象s1和經過序列化->反序列化步驟后的到的對象s2,并不是同一個對象,因此,出現(xiàn)了兩個實例,再次違背了單例模式的設計原則。
為了消除問題,在單例模式類中,實現(xiàn)Serializable接口之后 添加對readResolve()方法的實現(xiàn):當從I/O流中讀取對象時,readResolve()方法都會被調用到。實際上就是用readResolve()中返回的對象直接替換在反序列化過程中創(chuàng)建的對象,而被創(chuàng)建的對象則會被垃圾回收掉。這就確保了在序列化和反序列化的過程中沒人可以創(chuàng)建新的實例,修改后的代碼如下:
package SingletonTest;import java.io.Serializable;/**
* @author LearnAndGet
*
* @time 2018年11月13日
*
*/public class SingletonV6 implements Serializable{ //是否已經初始化過的標記位
private static boolean isInitialized = false;
//構造函數(shù)中,當實例已經被初始化時,不能繼續(xù)獲取新實例
private SingletonV6()
{ synchronized(SingletonV6.class)
{ if(isInitialized == false)
{
isInitialized = !isInitialized;
}else
{ throw new RuntimeException("單例模式被破壞...");
}
}
} static class SingetonGet
{ private static final SingletonV6 instance = new SingletonV6();
}
public static SingletonV6 getInstance()
{ return SingetonGet.instance;
} //實現(xiàn)readResolve方法
private Object readResolve()
{ return getInstance();
}
}
重新運行上述序列化和反序列過程,可以發(fā)現(xiàn),此時得到的對象是同一對象。
1118140819
1118140819
8.總結
在實際開發(fā)中,根據自己的需要,選擇對應的單例模式即可,不一樣非要實現(xiàn)第7節(jié)中那種無堅不摧的單例模式。畢竟不是所有場景下都需要實現(xiàn)序列化接口, 也并不是所有人都會用反射來破壞單例模式。因此比較常用的是第5節(jié)中的,內部類單例模式,代碼簡潔明了,且節(jié)省空間。
以上就是java單例模式實現(xiàn)的方法的詳細內容,更多關于java單例模式的資料請關注腳本之家其它相關文章!
相關文章
Spring的Bean注入解析結果BeanDefinition詳解
這篇文章主要介紹了Spring的Bean注入解析結果BeanDefinition詳解,BeanDefinition描述了一個bean實例,擁有屬性值、構造參數(shù)值和具體實現(xiàn)的其他信息,其是一個bean的元數(shù)據,xml中配置的bean元素會被解析成BeanDefinition對象,需要的朋友可以參考下2023-12-12
Java用POI解析excel并獲取所有單元格數(shù)據的實例
下面小編就為大家?guī)硪黄狫ava用POI解析excel并獲取所有單元格數(shù)據的實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-10-10
Java實現(xiàn)將導出帶格式的Excel數(shù)據到Word表格
在Word中制作報表時,我們經常需要將Excel中的數(shù)據復制粘貼到Word中,這樣則可以直接在Word文檔中查看數(shù)據而無需打開另一個Excel文件。本文將通過Java應用程序詳細介紹如何把帶格式的Excel數(shù)據導入Word表格。希望這篇文章能對大家有所幫助2022-11-11
SpringBoot環(huán)境下junit單元測試速度優(yōu)化方式
這篇文章主要介紹了SpringBoot環(huán)境下junit單元測試速度優(yōu)化方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
SpringBoot ThreadLocal實現(xiàn)公共字段自動填充案例講解
每一次在Controller層中封裝改動數(shù)據的方法時都要重新設置一些共性字段,顯得十分冗余。為了解決此問題也是在項目中第一次利用到線程,總的來說還是讓我眼前一亮,也開闊了視野,對以后的開發(fā)具有深遠的意義2022-10-10

