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<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单例模式的资料请关注我们其它相关文章!

(0)

相关推荐

  • PHP单例模式模拟Java Bean实现方法示例

    本文实例讲述了PHP单例模式模拟Java Bean实现方法.分享给大家供大家参考,具体如下: 问题: 根据如下杨辉三角形 实现一个get_value($row,$col)方法: (前一个由于代码是手机编辑的,很乱,重新发下)只是为了实现这个方法,很简单,几行代码就能实现,但如果行和列的值稍微大点,你就发现,运行时间很长.所以就这次的题做了个稍微复杂点的例子,说明下单例模式的使用.static的使用.模拟Java Bean.static的使用.递归函数案例等. /** * author Winte

  • 为何Java单例模式我只推荐两种

    双重检查模式 public class Singleton { private volatile static Singleton singleton; //1:volatile修饰 private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { //2:减少不要同步,优化性能 synchronized (Singleton.class) { // 3:同步,线程安全 if (sin

  • Java多线程实战之单例模式与多线程的实例详解

    1.立即加载/饿汉模式 // 立即加载/饿汉模式 public class MyObject { private static final MyObject myObject = new MyObject(); private MyObject() { } public static MyObject getInstance() { return myObject; } } 立即加载/饿汉模式是在类创建的同时已经创建好一个静态的对象供系统使用,不存在线程安全问题 2.延迟加载/懒汉模式 // 延

  • 你真的了解java单例模式了吗?

    一.背景 最近在学习设计模式,在看到单例模式的时候,我一开始以为直接很了解单例模式了,实现起来也很简单,但是实际上单例模式有着好几个变种,并且多线程中涉及到线程安全问题,那么本文我们就来好好聊聊单例模式,说一下经典三种实现方式:饿汉式.懒汉式.登记式.并且解决掉多线程中可能出现的线程安全问题. 二.基本概念 1.为什么要使用单例模式? 在我们日常的工作中,很多对象通常占用非常重要的系统资源,比如:IO处理,数据库操作等,那我们必须要限制这些对象只有且始终使用一个公用的实例,即单例. 2.单例模式

  • java使用静态关键字实现单例模式

    本文为大家分享了使用静态关键字实现单例模式的具体代码,供大家参考,具体内容如下 单例模式:只能获得某个类的唯一一个实例 单例模式,不管什么时间点得到的对象都是同一个对象 看下面代码: /** * 单例模式 * @author xiongda * @date 2018年4月15日 */ public class SingletonMode { private static SingletonMode single =null; public int number = 1; //将构造方法定义为私有

  • Java双重检查加锁单例模式的详解

    什么是DCL DCL(Double-checked locking)被设计成支持延迟加载,当一个对象直到真正需要时才实例化: class SomeClass { private Resource resource = null; public Resource getResource() { if (resource == null) resource = new Resource(); return resource; } } 为什么需要推迟初始化?可能创建对象是一个昂贵的操作,有时在已知的运

  • Java单例模式实现静态内部类方法示例

    Singleton是众多设计模式中最容易理解的一种,也是众多设计模式中较为重要的一种设计模式.接下来我们看看具体介绍. Singleton模式实现的重点在于将构造函数私有化(private),并通过提供静态公有函数(public synchronized static xxx getInstance)来获取定义在类中的静态私有成员(private static xxx instance),通过一个简单的判断静态实例是否为空来控制这个类只能够new一次,即控制了一个类只能有单个实例,一般的实现如下

  • 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; }

  • java单例模式实现面板切换

    本文实例为大家分享了java单例模式实现面板切换的具体代码,供大家参考,具体内容如下 1.首先介绍一下什么是单例模式: java单例模式是一种常见的设计模式,那么我们先看看懒汉模式: public class Singleton_ { //设为私有方法,防止被外部类引用或实例 private Singleton_(){ System.out.println("懒汉单例模式"); } private static Singleton_ single = null; //并对外只暴露get

  • Java单例模式实现的几种方式

    Java单例模式实现的几种方式 单例模式好多书上都是这么写的: public class SingleTon1 { private SingleTon1(){ } private static SingleTon1 instance = null; public static SingleTon1 getInstance(){ if(instance == null){ instance = new SingleTon1(); } return instance; } } 但是实际开发中是不会这

  • java 单例模式(饿汉模式与懒汉模式)

    java 单例模式 饿汉式单例 对于饿汉模式,我们可这样理解:该单例类非常饿,迫切需要吃东西,所以它在类加载的时候就立即创建对象. 懒汉式单例类 对于懒汉模式,我们可以这样理解:该单例类非常懒,只有在自身需要的时候才会行动,从来不知道及早做好准备.它在需要对象的时候,才判断是否已有对象,如果没有就立即创建一个对象,然后返回,如果已有对象就不再创建,立即返回. 单例设计模式常用于JDBC链接数据库 注意: 1 我们常用的是第一种饿汉式,因为: (1)既然采用了单例设计模式,就是为了使用单例类的对象

  • Java 单例模式的实现资料整理

    Java单例模式的实现,对java 单例模式的几种实现方法进行了整理: 单例模式好多书上都是这么写的: public class SingleTon1 { private SingleTon1(){ } private static SingleTon1 instance = null; public static SingleTon1 getInstance(){ if(instance == null){ instance = new SingleTon1(); } return insta

  • Java 单例模式线程安全问题

    Java 单例模式线程安全问题 SpringIOC容器默认提供bean的访问作用域是单例模式.即在整个application生命周期中,只有一个instance.因此在多线程并发下,会有线程安全风险.我们在MVC框架下的servlet就是线程安全的.由于该servlet是在客户端,多并发相对少,但是对于web service端,需要考虑到. ThreadLocal类:为每一个线程提供了一个独立的变量(实例)副本,从各将各个不同的实例访问isolation. 在同步锁机制中,后来者线程等待先行线程

  • java 单例模式(懒汉式与饿汉式)

    java 单例模式 单例模式是一种常用的软件设计模式.在它的可信结构中只包含一个被实例化单例的特殊类.通过单例设计模式可以把整系统中的一个类只有一个实例. 单例设计模式又分为两种方式,懒汉式和饿汉式. (1)懒汉式,就是只有当调用getInstance的时候,才会初始化这个单例. (2)饿汉式,就是一旦加载好类,就把单例初始化完成.即是调用getInstance的时候,单例是已经存在了. 代码如下: 懒汉式单例 /** * Description: * 一.设计了一个懒汉式单例 * * @aut

  • java 单例模式的实例详解

    java 单例模式的实例详解 概念: java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例.饿汉式单例.登记式单例三种. 单例模式有一下特点: 1.单例类只能有一个实例. 2.单例类必须自己自己创建自己的唯一实例. 3.单例类必须给所有其他对象提供这一实例. 单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例.在计算机系统中,线程池.缓存.日志对象.对话框.打印机.显卡的驱动程序对象常被设计成单例.这些应用都或多或少具有资源管理器的功能.每台计算机可以有若干个打

  • 23种设计模式(1) java单例模式

    23种设计模式第四篇:java单例模式 定义: 单例模式,是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例的特殊类.通过单例模式可以保证系统中一个类只有一个实例.即一个类只有一个对象实例. 特点: 1.单例类只能有一个实例. 2.单例类必须自己自己创建自己的唯一实例. 3.单例类必须给所有其他对象提供这一实例 单例模式的要点: 1.私有的构造方法     2.指向自己实例的私有静态引用     3.以自己实例为返回值的静态的公有的方法 单例模式根据实例化对象时机的不同分为两种: 一

随机推荐