java单例模式实现_java单例模式实现

1.最基本的单例模式

/***@authorLearnAndGet

* @time 2018年11月13日

* 最基本的单例模式*/

public classSingletonV1 {private static SingletonV1 instance = newSingletonV1();;//构造函数私有化

privateSingletonV1() {}public staticSingletonV1 getInstance()

{returninstance;

}

}

importorg.junit.Test;public classSingletonTest {

@Testpublic void test01() throwsException

{

SingletonV1 s1=SingletonV1.getInstance();

SingletonV1 s2=SingletonV1.getInstance();

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

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

}

}//运行结果如下:

589873731

589873731

2.类加载时不初始化实例的模式

上述单例模式在类加载的时候,就会生成实例,可能造成空间浪费,如果需要修改成,在需要使用时才生成实例,则可修改代码如下:

1 public classSingletonV2 {2

3 private staticSingletonV2 instance;4

5 //构造函数私有化

6 privateSingletonV2() {}7

8 public static SingletonV2 getInstance(){10 if(instance == null)11 {12 instance = newSingletonV2();13 }14 returninstance;15 }16 }

然而,上述方案虽然在类加载时不会生成实例,但是存在线程安全问题,如果线程A在执行到第10行时,线程B也进入该代码块,恰好也执行好第10行,此时如果实例尚未生成,则线程A和线程B都会执行第12行的代码,各自生成一个实例,此时就违背了单例模式的设计原则。实际测试代码如下:

public classSingletonTest {

@Testpublic void test02() throwsException

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

{

Thread th1= newgetInstanceThread();

th1.start();

}

}class getInstanceThread extendsThread

{public voidrun()

{try{

SingletonV2 s=SingletonV2.getInstance();

System.out.println(Thread.currentThread().getName()+" get Instance "+s.hashCode()+" Time: "+System.currentTimeMillis());

}catch(Exception e)

{

e.printStackTrace();

}

}

}

}

经过多次测试,可能产生如下输出结果:

791f23aa4c81996f6c7eda0102aa8e2f.png

3.线程安全的单例模式

在上述单例模式下进行改进,在getInstance方法前加入 Sychronized关键字,来实现线程安全,修改后代码如下:

1 public classSingletonV3 {2

3 private staticSingletonV3 instance;4

5 //构造函数私有化

6 privateSingletonV3() {}7

//synchronized关键字在静态方法上,锁定的是当前类:sychronized关键字

8 public static synchronizedSingletonV3 getInstance()9 {10 if(instance == null)11 {12 instance = newSingletonV3();13 }14 returninstance;15 }16 }

增加sychronized关键字后,确实能够改善线程安全问题,但是也带来了额外的锁开销。性能受到一定影响。举例来说,此时如果有1000个线程都需要使用SingletonV3实例,因为加锁的位置在getInstance上,因此,每个线程都必须等待其他获取了锁的线程完全执行完锁中的方法后,才能够进入该方法并获取自己的实例。

4.双重校检+线程安全单例模式

于是可以在上述代码的基础上,只有当Singleton实例未被初始化时,对实例化方法加锁即可。在Singleton实例已经被初始化时,无需加锁,直接返回当前Singleton对象。代码如下:

1 private staticSingletonV4 instance;2

3 //构造函数私有化

4 privateSingletonV4() {}5

6 public staticSingletonV4 getInstance()7 {8 if(instance == null)9 {10 synchronized(SingletonV4.class)11 {12 //双重校检

13 if(instance == null)14 {15 instance = newSingletonV4();16 }17 }18 }19 returninstance;20 }

5.内部类单例模式

尽管上述方案解决了同步问题,双重校检也使得性能开销大大减小,但是,只有有synchronized关键字的存在。性能多多少少还是会有一些影响,此时,我们想到了 "内部类"的用法。

①.内部类不会随着类的加载而加载

②.一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生。

静态内部类随着方法调用而被加载,只加载一次,不存在并发问题,所以是线程安全。基于此,修改代码如下:

/推荐指数:★★★★★

1 public classSingletonV5 {2 //构造函数私有化

3 privateSingletonV5() {}4

5 static classSingetonGet6 {7 private static final SingletonV5 instance = newSingletonV5();8 }9

10 public staticSingletonV5 getInstance()11 {12 returnSingetonGet.instance;13 }14 }

6.反射都不能破坏的单例模式

静态内部类实现的单例模式,是目前比较推荐的方式,但是在java功能强大反射的机制下,它就是个弟弟,此时利用反射仍然能够创建出多个实例,以下是创建实例的代码:

1 @Test2 public voidtest4()3 {4 //普通方式获取实例s1,s2

5 SingletonV5 s1 =SingletonV5.getInstance();6 SingletonV5 s2 =SingletonV5.getInstance();7 //利用反射获取实例s3,s4

8 SingletonV5 s3 = null;9 SingletonV5 s4 = null;10 try

11 {12 Class clazz = SingletonV5.class;13 Constructor constructor =clazz.getDeclaredConstructor();14 constructor.setAccessible(true);15 s3 =constructor.newInstance();16 s4 =constructor.newInstance();17 }catch(Exception e)18 {19 e.printStackTrace();20 }21

22 System.out.println(s1.hashCode());23 System.out.println(s2.hashCode());24 System.out.println(s3.hashCode());25 System.out.println(s4.hashCode());26 }

输出结果如下:

589873731

589873731

200006406

2052001577

可以看到,s1和s2拥有相同的哈希码,因此他们是同一个实例,但是s3、s4,是通过反射后用构造函数重新构造生成的实例,他们均与s1,s2不同。此时单例模式下产生了多个不同的对象,违反了设计原则。

基于上述反射可能造成的单例模式失效,考虑在私有的构造函数中添加是否初始化的标记位,使私有构造方法只可能被执行一次。

public classSingletonV6 {//是否已经初始化过的标记位

private static boolean isInitialized = false;//构造函数中,当实例已经被初始化时,不能继续获取新实例

privateSingletonV6()

{synchronized(SingletonV6.class)

{if(isInitialized == false)

{

isInitialized= !isInitialized;

}else{throw new RuntimeException("单例模式被破坏...");

}

}

}static classSingetonGet

{private static final SingletonV6 instance = newSingletonV6();

}public staticSingletonV6 getInstance()

{returnSingetonGet.instance;

}

}

测试代码如下:

@Testpublic voidtest5()

{

SingletonV6 s1=SingletonV6.getInstance();

SingletonV6 s2= null;try{

Class clazz = SingletonV6.class;

Constructor 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.(SingletonV6.java:26)

...28more2052001577

7.序列化反序列化都不能破坏的单例模式

经过上述改进,反射也不能够破坏单例模式了。但是,依然存在一种可能造成上述单例模式产生两个不同的实例,那就是序列化。当一个对象A经过序列化,然后再反序列化,获取到的对象B和A是否是同一个实例呢,验证代码如下:

/*** @Author {LearnAndGet}

* @Time 2018年11月13日

* @Discription:测试序列化并反序列化是否还是同一对象*/

packageSingletonTest;importjava.io.FileInputStream;importjava.io.FileOutputStream;importjava.io.ObjectInput;importjava.io.ObjectInputStream;importjava.io.ObjectOutput;importjava.io.ObjectOutputStream;public classMain {/***@paramargs*/

public static voidmain(String[] args) {//TODO Auto-generated method stub

SingletonV6 s1 =SingletonV6.getInstance();

ObjectOutput objOut= null;try{//将s1序列化(记得将Singleton实现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,并不是同一个对象,因此,出现了两个实例,再次违背了单例模式的设计原则。

为了消除问题,在单例模式类中,实现Serializable接口之后 添加对readResolve()方法的实现:当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象,而被创建的对象则会被垃圾回收掉。这就确保了在序列化和反序列化的过程中没人可以创建新的实例,修改后的代码如下:

packageSingletonTest;importjava.io.Serializable;/***@authorLearnAndGet

*

* @time 2018年11月13日

**/

public class SingletonV6 implementsSerializable{//是否已经初始化过的标记位

private static boolean isInitialized = false;//构造函数中,当实例已经被初始化时,不能继续获取新实例

privateSingletonV6()

{synchronized(SingletonV6.class)

{if(isInitialized == false)

{

isInitialized= !isInitialized;

}else{throw new RuntimeException("单例模式被破坏...");

}

}

}static classSingetonGet

{private static final SingletonV6 instance = newSingletonV6();

}public staticSingletonV6 getInstance()

{returnSingetonGet.instance;

}//实现readResolve方法

privateObject readResolve()

{returngetInstance();

}

}

重新运行上述序列化和反序列过程,可以发现,此时得到的对象是同一对象。

1118140819

1118140819

8.总结

在实际开发中,根据自己的需要,选择对应的单例模式即可,不一样非要实现第7节中那种无坚不摧的单例模式。毕竟不是所有场景下都需要实现序列化接口, 也并不是所有人都会用反射来破坏单例模式。因此比较常用的是第5节中的,内部类单例模式,代码简洁明了,且节省空间。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值