单例模式是一种设计模式,是在整个运行过程中只需要产生一个实例。那么怎样去创建呢,以下提供了几种方案。

一、创建单例对象

懒汉式

  1. public class TestSingleton {
  2. // 构造方法私有化
  3. private TestSingleton(){}
  4. // 声明实例
  5. private static TestSingleton singleton;
  6. // 提供外部调用方法,生成并获取实例
  7. public static TestSingleton getInstance() {
  8. if(singleton == null) {
  9. singleton = new TestSingleton();
  10. }
  11. return singleton;
  12. }
  13. }

此方案是以时间换空间,启动时并不会执行任何操作,只有被调用时,采取实例化对象。不过这种方法在多线程下不安全,因为两个线程如果同时调用时,会同时通过非空验证的验证,造成创建两个对象的后果,有悖设计初衷。

针对多线程问题,应该加入双重非空判断:

  1. public class TestSingleton {
  2. // 构造方法私有化
  3. private TestSingleton(){}
  4. // 声明实例
  5. private static volatile TestSingleton singleton;
  6. // 提供外部调用方法,生成并获取实例
  7. public static TestSingleton getInstance() {
  8. if(singleton == null) {
  9. synchronized (TestSingleton.class) {
  10. if(singleton == null) {
  11. singleton = new TestSingleton();
  12. }
  13. }
  14. }
  15. return singleton;
  16. }
  17. }

饿汉式

  1. public class TestSingleton {
  2. // 构造方法私有化
  3. private TestSingleton(){}
  4. // 声明并生成实例
  5. private static TestSingleton singleton = new TestSingleton();
  6. // 提供外部调用方法,获取实例
  7. public static TestSingleton getInstance() {
  8. return singleton;
  9. }
  10. }

以空间换时间,类一加载时,就对其进行实例化,后面调用时直接提供对象实例。

静态内部类实现懒加载

  1. public class TestSingleton {
  2. // 构造方法私有化
  3. private TestSingleton(){}
  4. private static class TestSingletonFactory{
  5. private static TestSingleton singleton = new TestSingleton();
  6. }
  7. // 提供外部调用方法,获取实例
  8. public static TestSingleton getInstance() {
  9. return TestSingletonFactory.singleton;
  10. }
  11. }

当getInstance方法被调用时,才会初始化静态内部类TestSingletonFactory的静态变量singleton。此处由JVM来保障线程安全。

二、破坏单例

实现单例后,按照预期结果应该所有对象都是同一个对象。但是以下有几种情况可以破坏单例的性质。

首先让单例类实现Serializable, Cloneable接口,以便实验。

  1. public class TestSingleton implements Serializable, Cloneable{
  2. private static final long serialVersionUID = 1L;
  3. // 构造方法私有化
  4. private TestSingleton(){}
  5. private static class TestSingletonFactory{
  6. private static TestSingleton singleton = new TestSingleton();
  7. }
  8. // 提供外部调用方法,获取实例
  9. public static TestSingleton getInstance() {
  10. return TestSingletonFactory.singleton;
  11. }
  12. }
  • 序列化
  1. // 获取实例
  2. TestSingleton originSingleton = TestSingleton.getInstance();
  3. // 写出对象
  4. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  5. ObjectOutputStream oos = new ObjectOutputStream(bos);
  6. oos.writeObject(originSingleton);
  7. // 写入对象
  8. ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
  9. ObjectInputStream ois = new ObjectInputStream(bis);
  10. TestSingleton serializeSingleton = (TestSingleton) ois.readObject();
  11. // 判断两个对象是否相等
  12. System.out.println(originSingleton == serializeSingleton); // false
  • 反射
  1. // 反射
  2. Class<TestSingleton> clazz = TestSingleton.class;
  3. // 获取无参构造函数
  4. Constructor<TestSingleton> constructor = clazz.getDeclaredConstructor();
  5. // 将私有设置为可见
  6. constructor.setAccessible(true);
  7. // 用构造器生成实例
  8. TestSingleton instance = constructor.newInstance();
  9. // 判断两个对象是否相等
  10. System.out.println(originSingleton == instance); // false
  • 克隆
  1. // 克隆
  2. TestSingleton clone = (TestSingleton) originSingleton.clone();
  3. System.out.println(originSingleton == clone); // false

三、修复破坏

对于这种预料之外的结果,我们应该怎样去控制呢?

  • 序列化

添加readResolve方法,返回Object。

  • 反射

添加全局可见变量,如果再次调用构造方法生成实例时,抛出运行时错误。

  • 克隆

重写clone方法,直接返回单例对象。

  1. public class TestSingleton implements Serializable, Cloneable{
  2. private static final long serialVersionUID = 1L;
  3. private static volatile boolean isCreated = false;//默认是第一次创建
  4. // 构造方法私有化
  5. private TestSingleton(){
  6. if(isCreated) {
  7. throw new RuntimeException("实例已经被创建");
  8. }
  9. isCreated = true;
  10. }
  11. private static class TestSingletonFactory{
  12. private static TestSingleton singleton = new TestSingleton();
  13. }
  14. // 提供外部调用方法,获取实例
  15. public static TestSingleton getInstance() {
  16. return TestSingletonFactory.singleton;
  17. }
  18. @Override
  19. protected Object clone() throws CloneNotSupportedException {
  20. return getInstance();
  21. }
  22. /**
  23. * 防止序列化破环
  24. * @return
  25. */
  26. private Object readResolve() {
  27. return getInstance();
  28. }
  29. }

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单例模式 直接讲实现单例模式的两种方法:懒汉式和饿汉式,单例模式的概念自己上 ...

随机推荐

  1. Vuex mapGetter的基本使用

    getter相当于Vuex中的计算属性 对 state 做处理再返回 mapGetters 把 Store 中的 getters 映射到组件中的计算属性中 Store文件 import Vue fro ...

  2. Homekit_二路继电器

    介绍一款二路继电器,使用Homekit进行控制,有兴趣的可以去以下链接看看: https://item.taobao.com/item.htm?spm=a1z10.1-c.w4004-11265006 ...

  3. JDK8 String类知识总结

    一.概述 java的String类可以说是日常实用的最多的类,但是大多数时候都只是简单的拼接或者调用API,今天决定深入点了解一下String类. 要第一时间了解一个类,没有什么比官方的javaDoc ...

  4. js利用canvas绘制爱心

    js代码如下: var cav = document.getElementById("a").getContext("2d"); function draw(x ...

  5. 记一次mysql数据库被勒索(上)

    家里搞了台旧电脑做NAS,安装了nextcloud,选择了mysql做为数据库. 当时也没有想太多,mysql数据库密码随便设置了个123456,用的一切正常. 然后,听说可以找电信申请换个公网IP的 ...

  6. Kubernetes实战指南(三十三):都0202了,你还在手写k8s的yaml文件?

    目录 1. k8s的yaml文件到底有多复杂 2. 基于图形化的方式自动生成yaml 2.1 k8s图形化管理工具Ratel安装 2.2 使用Ratel创建生成yaml文件 2.2.1 基本配置 2. ...

  7. Linux 查网关和dns命令

    一,查看网关(缺省路由)方法: 1.route -n 或netstat -rn2.ip route show  二, 查看DNS: nslookup www.baidu.com

  8. day35:线程队列&进程池和线程池&回调函数&协程

    目录 1.线程队列 2.进程池和线程池 3.回调函数 4.协程:线程的具体实现 5.利用协程爬取数据 线程队列 1.线程队列的基本方法 put 存 get 取 put_nowait 存,超出了队列长度 ...

  9. linux 强制重启!

    原文链接:https://www.cnblogs.com/wipy/p/4261472.html 有时候,linux 由于硬盘或者其它原因, 某个进程挂住了,怎么也杀不死, 输入 reboot 命令也 ...

  10. c++: internal compiler error: Killed (program cc1plus)

    转自https://blog.csdn.net/qq_27148893/article/details/88936044 这是在开发板上编译opencv的时候报了一个错,主要是在编译过程中,内存不够造 ...