zoukankan      html  css  js  c++  java
  • 单例模式

    1.单例模式是什么

    单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通
    单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例

    保证一个类仅有一个实例,并提供一个访问它的全局访问点。

    2.单例模式解决了什么问题

    1.节省资源

    节省内存资源,减少无谓GC的消耗.

    2.防止冲突

    如果一个项目中有多个实例,会造成系统冲突。保证同一时间状态唯一。

    3.单例模式用法

    1.恶汉单例

    恶汉模式:加载时就初始化对象。

    1.静态代码块初始化

    public class HungrySingleton {
    	 private static  HungrySingleton hungrySingleton;
    
    	 static {
    		 hungrySingleton = new HungrySingleton();
    	 }
    
    	 private HungrySingleton (){}
    
    	 public static HungrySingleton getInstance() {  
         return hungrySingleton;  
         }
    }
    

    2.常量初始化

    public class HungrySingleton {
    	 private static final HungrySingleton hungrySingleton = new HungrySingleton();
    
    	 private HungrySingleton (){}
    
    	 public static HungrySingleton getInstance() {  
         return hungrySingleton;  
         }
    }
    

      此方法由于成员变量修饰为final所以优于静态代码块初始化。但是这两种单例模式有共同的缺点。在类加载时完成初始化将占用一定内存空间,占用的内存空间不会被GC回收。如果项目中一次都没有使用该对象。那么该对象会造成内存浪费。那么我们就需要一种‘懒加载’来实现加载。

    2.懒汉单例

    懒汉模式:调用时才初始化对象。

    1.标准懒汉单例

    public class StandardLazySingleton {
    	private static StandardLazySingleton standardLazySingleton;
    
    	/**
    	 * 私有构造方法
    	 */
    	private StandardLazySingleton() {
    	}
    	public static StandardLazySingleton getInstance() {
    		if (standardLazySingleton == null) {
    			standardLazySingleton = new StandardLazySingleton();
    		}
    		return standardLazySingleton;
    	}
    }
    
    

      此种方式的单例模式拥有静态成员变量,可能会出现并发问题。下面是几种变种解决方案。

    2.并发环境下懒汉单例

      直接在获取实例方法上加锁

    public class LazySingleton {
    
    	private static LazySingleton lazySingleton;
    
    	private LazySingleton() {
    	}
    	public static synchronized LazySingleton getInstance() {
    		if (lazySingleton == null) {
    			lazySingleton = new LazySingleton();
    		}
    		return lazySingleton;
    	}
    
    }
    

      此方式解决了‘懒加载’的问题,也解决了同步的问题。但是方法上全部加同步造成的效率很低。大多数情况下方法是不需要同步的。

    缩小锁定范围.

    public class LazySingleton {
    
    	private static LazySingleton lazySingleton;
    
    	private LazySingleton() {
    	}
    	public static LazySingleton getInstance() {
    		if (lazySingleton == null) {
    			synchronized(LazySingleton.class) {
    				lazySingleton = new LazySingleton();
    			}
    		}
    		return lazySingleton;
    	}
    
    }
    

      此方式缩小了锁定范围,但是同步会出现问题。假如一个线程进入了if (lazySingleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。我们可以在内部再次添加一个if (lazySingleton == null) 的判断。

    
    public class LazySingleton {
    
    	private static volatile LazySingleton lazySingleton;
    
    	private LazySingleton() {
    	}
    	public static LazySingleton getInstance() {
    		if (lazySingleton == null) {
    			synchronized(LazySingleton.class) {
    				if (lazySingleton == null) {
    					lazySingleton = new LazySingleton();
    				}
    			}
    		}
    		return lazySingleton;
    	}
    }
    

      以上方式为懒汉模式中推荐的单例使用方式。解决了线程同步的问题,提高了效率。这种方式有个专业的名词叫双重检查(Double-Check)

    volatile 关键词的作用:对变量的单次读/写操作可以保证原子性。

      首先我们需要了解jvm实例化一个对象需要哪些过程。
    1.分配内存空间。2.初始化对象。3.将内存空间的地址赋值给对应的引用。 由于jvm会对指令进行重新排序2和3的顺序可能打乱。当3在2之前时,在多线程环境下可能会暴露出一个没有对象的引用,从而导致不可预料的后果。为了防止这个问题,我们需要将变量设置为volatile类型的变量。保证其指令的原子性。需要特别注意的是volatile关键字在java1.5之前并不能产生这个功能。

    3.静态内部类单例

    public class StaticInnerClassSingleton {
    
    	private static class SingletonHolder {
    		private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
    	}
    
    	private StaticInnerClassSingleton() {
    	}
    
    	public static final StaticInnerClassSingleton getInstance() {
    		return SingletonHolder.INSTANCE;
    	}
    }
    

      一个类的静态属性只会在第一加载类时初始化。这是jvm保证的。所以无需担心并发问题。初始化进行一半的时候,别的线程是无法使用的,因为JVM会帮我们强行同步这个过程。静态变量智慧初始化一次,所以也能保证单例。静态内部类方式在StaticInnerClassSingleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonHolder类,从而完成StaticInnerClassSingleton的实例化。实现了‘懒加载’。但是如果我们访问了其他静态属性这个也会被实例化。

    4.容器单例

      大名鼎鼎的spring就是将每一个实现类作为一个bean装入容器中从而实现了单例。下面我们山寨一个简易的容器方式的单例。此种方式能解决单例中的大部分问题,但是不能解决懒加载的问题。然而这种方式极大的降低了代码之间的耦合。

    public class ContainerSingleton {
    
    	private static Map<String, Object> objMap = new HashMap<String, Object>();
    
    	private ContainerSingleton() {
    	}
    
    	public static void registerService(String key, Object instance) {
    		if (!objMap.containsKey(key)) {
    			objMap.put(key, instance);
    		}
    	}
    	public static Object getService(String key) {
    		return objMap.get(key);
    	}
    }
    

    需要在初始化项目的过程中将单例放入容器。

    5.枚举单例

      

    public enum SomeThing {
        INSTANCE;
        private Resource instance;
        SomeThing() {
            instance = new Resource();
        }
        public Resource getInstance() {
            return instance;
        }
    }
    
    

      需要用SomeThing.INSTANCE.getInstance()。首先,在枚举中我们明确了构造方法限制为私有,在我们访问枚举实例时会执行构造方法,同时每个枚举实例都是static final类型的,也就表明只能被实例化一次。在调用构造方法时,我们的单例被实例化。
    也就是说,因为enum中的实例被保证只会被实例化一次,所以我们的INSTANCE也被保证实例化一次。
    可以看到,枚举实现单例还是比较简单的,除此之外我们再来看一下Enum这个类的声明:

    public abstract class Enum<E extends Enum<E>>
            implements Comparable<E>, Serializable
    

      可以看到,枚举也提供了序列化机制。保证序列化和反序列化是一个对象。
    可以这么说,单元素枚举是目前实现单例模式的最佳方法。

    4.单例模式的问题

    1.不同类加载产生多个实例

    (巨坑待填)

    2.序列化反序列化产生多个实例

    (巨坑待填)

    5.单例模式总结

      单例模式是设计模式中相对简单的设计模式,简单的设计模式也有许多复杂的逻辑.在不同的场景中有不同的用法和坑。面对技术要保持谦虚的心态,戒骄戒躁扎实基础。

    引用

    http://blog.csdn.net/yy254117440/article/details/52305175
    https://www.cnblogs.com/zhaoyan001/p/6365064.html
    https://www.cnblogs.com/zuoxiaolong/p/pattern2.html

  • 相关阅读:
    Storm的并行度、Grouping策略以及消息可靠处理机制简介
    storm入门原理介绍
    Kafka学习笔记-Java简单操作
    批量复制word文档,并生成以日期为后缀名的批量文档攻略,批量生成word文档
    数组
    分支结构,循环结构学习整理
    java中的运算符
    Java中的变量和基本数据类型知识
    Java开发环境描述
    使用Map,统计字符串中每个字符出现的次数
  • 原文地址:https://www.cnblogs.com/yanlong300/p/7987301.html
Copyright © 2011-2022 走看看