热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

java单例模式实现的方法

这篇文章主要介绍了如何在JAVA中实现单例模式,文中代码简单易懂,供大家参考学习,感兴趣的小伙伴可以了解下

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();
 }
 }
 }
 
}

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

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 clazz = SingletonV5.class;
 Constructor 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 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)
 ... 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单例模式的资料请关注其它相关文章!


推荐阅读
  • 项目运行环境配置及可行性分析
    本文介绍了项目运行环境配置的要求,包括Jdk1.8、Tomcat7.0、Mysql、HBuilderX等工具的使用。同时对项目的技术可行性、操作可行性、经济可行性、时间可行性和法律可行性进行了分析。通过对数据库的设计和功能模块的设计,确保系统的完整性和安全性。在系统登录、系统功能模块、管理员功能模块等方面进行了详细的介绍和展示。最后提供了JAVA毕设帮助、指导、源码分享和调试部署的服务。 ... [详细]
  • 模块化区块链生态系统的优势概述及其应用案例
    本文介绍了相较于单体区块链,模块化区块链生态系统的优势,并以Celestia、Dymension和Fuel等模块化区块链项目为例,探讨了它们解决可扩展性和部署问题的方案。模块化区块链架构提高了区块链的可扩展性和吞吐量,并提供了跨链互操作性和主权可扩展性。开发人员可以根据需要选择执行环境,并获得奖学金支持。该文对模块化区块链的应用案例进行了介绍,展示了其在区块链领域的潜力和前景。 ... [详细]
  • 初探PLC 的ST 语言转换成C++ 的方法
    自动控制软件绕不开ST(StructureText)语言。它是IEC61131-3标准中唯一的一个高级语言。目前,大多数PLC产品支持ST ... [详细]
  • php还能用多少年(php还行吗)
    导读:很多朋友问到关于php还能用多少年的相关问题,本文编程笔记就来为大家做个详细解答,供大家参考,希望对大家有所帮助!一起来看看吧!本文目录一览: ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • 本文介绍了在Java开发中创建子包(package)的正确步骤,并解析了可能出现的错误情况。其中包括第一种错误情况的解决方法,以及在空包下只建一个包时可能出现的问题及解决方法。通过多建几个包,可以让IDE自动将父包提取出来,形成正确的层次结构。 ... [详细]
  • 如何实现JDK版本的切换功能,解决开发环境冲突问题
    本文介绍了在开发过程中遇到JDK版本冲突的情况,以及如何通过修改环境变量实现JDK版本的切换功能,解决开发环境冲突的问题。通过合理的切换环境,可以更好地进行项目开发。同时,提醒读者注意不仅限于1.7和1.8版本的转换,还要适应不同项目和个人开发习惯的需求。 ... [详细]
  • Activiti7流程定义开发笔记
    本文介绍了Activiti7流程定义的开发笔记,包括流程定义的概念、使用activiti-explorer和activiti-eclipse-designer进行建模的方式,以及生成流程图的方法。还介绍了流程定义部署的概念和步骤,包括将bpmn和png文件添加部署到activiti数据库中的方法,以及使用ZIP包进行部署的方式。同时还提到了activiti.cfg.xml文件的作用。 ... [详细]
  • Hibernate延迟加载深入分析-集合属性的延迟加载策略
    本文深入分析了Hibernate延迟加载的机制,特别是集合属性的延迟加载策略。通过延迟加载,可以降低系统的内存开销,提高Hibernate的运行性能。对于集合属性,推荐使用延迟加载策略,即在系统需要使用集合属性时才从数据库装载关联的数据,避免一次加载所有集合属性导致性能下降。 ... [详细]
  • 使用J2SE模拟MVC模式开发桌面应用程序的工程包的介绍
    以我开发过的一个娱乐管理系统为例:下图为我系统的业务逻辑的MVC流程:下图为以Eclipse开发中各包的说明:转载于:https:blog ... [详细]
  • 开发笔记:spring boot项目打成war包部署到服务器的步骤与注意事项
    本文介绍了将spring boot项目打成war包并部署到服务器的步骤与注意事项。通过本文的学习,读者可以了解到如何将spring boot项目打包成war包,并成功地部署到服务器上。 ... [详细]
  • 《计算机专业英语基础知识》由会员分享,可在线阅读,更多相关《计算机专业英语基础知识(25页珍藏版)》请在人人文库网上搜索。1、专业英语知识补充,本章学习 ... [详细]
  • nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd ... [详细]
  • PHP调试凶器Xdebug安装配置教程:PHP调试利器Xdebug安装配置教程作者:zhanhailiang日期:2013-03-111.简述引用官方描述:TheXdebugext ... [详细]
  • 爬虫框架Scrapy(三)
    正文共:2957字6图预计阅读时间:8分钟每日分享Bethetypeofpersonyouwanttomeet.努力变成理想的模样。小闫笔记࿱ ... [详细]
author-avatar
韩国高防服务器
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有