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关键字在静态方法上,锁定的是当前类:sychronized关键字
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 more
2052001577

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单例模式实现的更多相关文章

  1. 用java单例模式实现面板切换

    1.首先介绍一下什么是单例模式: java单例模式是一种常见的设计模式,那么我们先看看懒汉模式: public class Singleton_ { //设为私有方法,防止被外部类引用或实例 priv ...

  2. 【深入】java 单例模式(转)

    [深入]java 单例模式 关于单例模式的文章,其实网上早就已经泛滥了.但一个小小的单例,里面却是有着许多的变化.网上的文章大多也是提到了其中的一个或几个点,很少有比较全面且脉络清晰的文章,于是,我便 ...

  3. 深入Java单例模式(转)

    深入Java单例模式 源自 http://devbean.blog.51cto.com/448512/203501 在GoF的23种设计模式中,单例模式是比较简单的一种.然而,有时候越是简单的东西越容 ...

  4. Java 单例模式的七种写法

    Java 单例模式的七种写法 第一种(懒汉,线程不安全) public class Singleton { private static Singleton instance; private Sin ...

  5. java单例模式之懒汉式分析

    转自:http://blog.csdn.net/withiter/article/details/8140338 今天中午闲着没事,就随便写点关于Java单例模式的.其实单例模式实现有很多方法,这里我 ...

  6. Java 单例模式探讨

    以下是我再次研究单例(Java 单例模式缺点)时在网上收集的资料,相信你们看完就对单例完全掌握了 Java单例模式应该是看起来以及用起来简单的一种设计模式,但是就实现方式以及原理来说,也并不浅显哦. ...

  7. 单例模式:Java单例模式的几种写法及它们的优缺点

    总结下Java单例模式的几种写法: 1. 饿汉式 public class Singleton { private static Singleton instance = new Singleton( ...

  8. 9种Java单例模式详解(推荐)

    单例模式的特点 一个类只允许产生一个实例化对象. 单例类构造方法私有化,不允许外部创建对象. 单例类向外提供静态方法,调用方法返回内部创建的实例化对象.  懒汉式(线程不安全) 其主要表现在单例类在外 ...

  9. 你真的理解了java单例模式吗?讲别人都忽略的细节!

    前言:老刘这篇文章敢做保证,java的单例模式讲的比大多数的技术博客都要好,讲述别人技术博客都没有的细节!!! 1 java单例模式 直接讲实现单例模式的两种方法:懒汉式和饿汉式,单例模式的概念自己上 ...

  10. Java 单例模式详解

    概念: java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例.饿汉式单例.登记式单例三种. 单例模式有一下特点: 1.单例类只能有一个实例. 2.单例类必须自己自己创建自己的唯一实例. ...

随机推荐

  1. Hyperledger Fabric(5)ChainCode的编写步骤

    链码(chaincode) 会对 Fabric应用程序  发送的交易做出响应,执行代码逻辑,与 账本 进行交互. 再复习下他们之间的逻辑关系: Hyperledger Fabric 中,Chainco ...

  2. 第十章、numpy模块

    目录 第十章.numpy模块 一.导入方式 二.作用 三.通过函数创建numpy数组 四. numpy数组运算 五.重点 第十章.numpy模块 一.导入方式 import numpy as np#约 ...

  3. 使用eclipse根据wsdl生成客户端

    1.在需要生成的java项目右键new →other→ Web Service Client . 2.点击 Next.进入下面的界面,选择Brown...,选择WSDL,然后选择Next 3.Serv ...

  4. SQL 语句 连接

    SQL连接可以分为内连接.外连接.交叉连接. 数据库数据:             book表                                          stu表 1.内连接 ...

  5. 十,StatefulSet简介及简单使用

    目录 StatefulSet简介 为什么要用statefulset控制器 简单测试 使用 StatefulSet 创建基础的PV StatefulSet 清单 statefulset管理pod的启停顺 ...

  6. 解决国内安装tensorflow, opencv等安装不成功或下载太慢问题

    解决国内安装tensorflow, opencv等安装不成功或下载太慢问题 复制自博客:https://blog.csdn.net/jorg_zhao/article/details/80075293 ...

  7. python学习笔记(三)条件判断和循环

    1.条件判断语句 Python中条件选择语句的关键字为:if .elif .else这三个.其基本形式如下: 1 2 3 4 5 6 7 8 9 age_of_cc = 27   age = int( ...

  8. netty-1.从一个最简单的例子开始

    (原) 第一篇,从一个最简单的例子开始 1.netty是干什么,怎么用,这里不作介绍,先从一个例子来了解它, netty 5.0以上的版本被废弃了,以下例子从4.1.10.Final版本开始. 2.一 ...

  9. GIT和SVN的区别(面试)

    Cit是分布式,而SVN不是分布式 存储内容的时候,Git按元数据方式存储,而SVN是按文件 Git没有一个全局版本号,SVN有,目前为止这是SVN相比Git缺少的最大的一个特征 Git的内容完整性要 ...

  10. 解决laydate动态设置初始值的问题

    //初始化//注意:我这里是时间范围选择,所以定义了range属性.var timeScope = laydate.render({ elem: '#time_scope', range: '~', ...