欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

java单例模式实现的方法

程序员文章站 2022-05-06 23:05:47
1.最基本的单例模式/** * @author learnandget * @time 2018年11月13日 * 最基本的单例模式 */public class singletonv1 { pri...

1.最基本的单例模式

/**
 * @author learnandget
 * @time 2018年11月13日
 * 最基本的单例模式 */public class singletonv1 { 
 private static singletonv1 instance = new singletonv1();; 
 //构造函数私有化
 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; //构造函数私有化 
 private singletonv2() {} 
	 public static singletonv2 getinstance()
	 { if(instance == null) 
 { instance = new singletonv2();
	} 
	return instance; } 
	}

然而,上述方案虽然在类加载时不会生成实例,但是存在线程安全问题,如果线程a在执行到第10行时,线程b也进入该代码块,恰好也执行好第10行,此时如果实例尚未生成,则线程a和线程b都会执行第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();
 }
 }
 }
 
}

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

java单例模式实现的方法

3.线程安全的单例模式

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

 public class singletonv3 { 
 
 private static singletonv3 instance; 
 
 //构造函数私有化 
 private singletonv3() {} 
 
    //synchronized关键字在静态方法上,锁定的是当前类: 
 public static synchronized singletonv3 getinstance() 
 {
 if(instance == null) 
 
{
 instance = new singletonv3();
 }
 return instance;
 }
 }

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

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

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

 private static singletonv4 instance; 
 
 //构造函数私有化 
 private singletonv4() {}
 
 public static singletonv4 getinstance() 
 { 
 if(instance == null) 
 
 {
 synchronized(singletonv4.class) 
 {
 //双重校检
 if(instance == null) 
 {
  instance = new singletonv4();
  }
 }
 }
 return instance;
 }

5.内部类单例模式 

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

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

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

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

 public class singletonv5 {
 //构造函数私有化
 private singletonv5() {} 
 
 static class singetonget 
 { 
 private static final singletonv5 instance = new singletonv5(); 
 } 
 
 public static singletonv5 getinstance() 
 {
 return singetonget.instance;
 }
 }

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

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

 @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,是通过反射后用构造函数重新构造生成的实例,他们均与s1,s2不同。此时单例模式下产生了多个不同的对象,违反了设计原则。

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

public class singletonv6 { //是否已经初始化过的标记位
 private static boolean isinitialized = false; 
 //构造函数中,当实例已经被初始化时,不能继续获取新实例
 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实现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()中返回的对象直接替换在反序列化过程中创建的对象,而被创建的对象则会被垃圾回收掉。这就确保了在序列化和反序列化的过程中没人可以创建新的实例,修改后的代码如下:

package singletontest;import java.io.serializable;/**
 * @author learnandget
 *
 * @time 2018年11月13日
 * 
 */public class singletonv6 implements serializable{ //是否已经初始化过的标记位
 private static boolean isinitialized = false; 
 //构造函数中,当实例已经被初始化时,不能继续获取新实例
 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;
 } //实现readresolve方法
 private object readresolve() 
 { return getinstance();
 }
}

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

1118140819
1118140819

8.总结

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

以上就是java单例模式实现的方法的详细内容,更多关于java单例模式的资料请关注其它相关文章!