Design_pattern之单例模式
2016-04-03 18:07
281 查看
许多系统只需要一个全局的对象,有利于协调系统整体的行为。这就需要单例模式。
单例模式最大的难点在于,确保线程安全。
单例模式大体分为两种:饿汉式和懒汉式 。
饿汉式适用于那些占用内存小,初始化快的对象;在开始直接初始化,不会出现线程安全问题;
而懒汉式适用于这个对象用的不是太频繁或者只在特定场景使用,或者占用资源较大,初始化慢;
懒汉式存在线程安全的问题,需要加锁。
而加锁就会降低性能。如果每次获取都要同步就必然影响性能。
因此懒汉式又可以分为
-1.Double Check Lock (DCL)方式 是使用最多的单例方式。
第一层判空主要为了不必要的同步;第二层判空为了在null的时候创建实例。
其中要注意volatile 关键字;这是由于instance=newSingleton_doubleValid();并不是一个原子操作,他被拆分成多条汇编指令,包括
- 分配内存
- 初始化成员和构造方法
- instance指向内存(此时i已经nstance!=null)
由于java编译器允许乱序执行,java内存模型(JMM)无法保证按顺序执行上边的操作,因此当1-3-2执行,线程A执行到3而线程B开始执行,就会出现问题。这就是DCL失效问题。在JDK1.5之后java具体化了了volatile 关键字,保证instance 每次从主内存中读取。
2,静态内部类的单例
在java并发编程实践中,支出DCL失效问题,不赞成使用,而是建议使用静态内部类的方式:
当第一次加载类的时候不会初始化,只有在第一次调用getInstance方法的时候才会初始化(虚拟机会加载holder类)。不仅保证了线程安全,也保证了单利的唯一性,延迟了单例的实例化。
3 枚举方式
枚举和普通的类一样,可以有字段和方法。但他是默认线程安全的,任何时候只偶有一个实例。写法简单是他最大的有点。
上述所有方法在反序列化的时候会重新生成新的对象。而枚举不会。具体不太懂。
4 容器方式
这是一个单例的管理类,在程序的初始将许多单例类型存入map中,使用的时候通过key取出。使用统一的接口操作,隐藏了具体实现,降低耦合。
不管方式,核心都是私有构造方法,静态获取唯一。这个过程需要保证线程安全,防止反序列化。具体根据并发环境,资源消耗等选择。
单例模式最大的难点在于,确保线程安全。
单例模式大体分为两种:饿汉式和懒汉式 。
饿汉式适用于那些占用内存小,初始化快的对象;在开始直接初始化,不会出现线程安全问题;
public class Singleton_ehan { //饿汉式 private static Singleton_ehan instance=new Singleton_ehan(); private Singleton_ehan(){} public static Singleton_ehan getInstance(){ return instance; } }
而懒汉式适用于这个对象用的不是太频繁或者只在特定场景使用,或者占用资源较大,初始化慢;
public class Sington_lanhan { //懒汉式 private static Sington_lanhan instance=null; private Sington_lanhan(){} public static Sington_lanhan getInstance(){ if(instance==null){ instance=new Sington_lanhan(); } return instance; } }
懒汉式存在线程安全的问题,需要加锁。
public class Singleton_lock { private static Singleton_lock instance=null; private Singleton_lock(){} public Singleton_lock getInstance(){ synchronized(Singleton_lock.class){ if(instance==null){ instance=new Singleton_lock(); } } return instance; } }
而加锁就会降低性能。如果每次获取都要同步就必然影响性能。
因此懒汉式又可以分为
-1.Double Check Lock (DCL)方式 是使用最多的单例方式。
第一层判空主要为了不必要的同步;第二层判空为了在null的时候创建实例。
其中要注意volatile 关键字;这是由于instance=newSingleton_doubleValid();并不是一个原子操作,他被拆分成多条汇编指令,包括
- 分配内存
- 初始化成员和构造方法
- instance指向内存(此时i已经nstance!=null)
由于java编译器允许乱序执行,java内存模型(JMM)无法保证按顺序执行上边的操作,因此当1-3-2执行,线程A执行到3而线程B开始执行,就会出现问题。这就是DCL失效问题。在JDK1.5之后java具体化了了volatile 关键字,保证instance 每次从主内存中读取。
public class Singleton_doubleValid { private volatile static Singleton_doubleValid instance=null; private Singleton_doubleValid(){} public static Singleton_doubleValid getInstance(){ if(instance==null){ synchronized (this){ if(instance==null){ instance=new Singleton_doubleValid(); } } } return instance; } }
2,静态内部类的单例
在java并发编程实践中,支出DCL失效问题,不赞成使用,而是建议使用静态内部类的方式:
当第一次加载类的时候不会初始化,只有在第一次调用getInstance方法的时候才会初始化(虚拟机会加载holder类)。不仅保证了线程安全,也保证了单利的唯一性,延迟了单例的实例化。
public class Singleton_innerClass { //静态内部类 在加载的时候初始化实例 private static class Sinleton_innerClass_Holder{ private static Singleton_innerClass instance=new Singleton_innerClass(); } private Singleton_innerClass(){} public Singleton_innerClass getInstance(){ return Sinleton_innerClass_Holder.instance; } }
3 枚举方式
枚举和普通的类一样,可以有字段和方法。但他是默认线程安全的,任何时候只偶有一个实例。写法简单是他最大的有点。
上述所有方法在反序列化的时候会重新生成新的对象。而枚举不会。具体不太懂。
public enum Singleton_Enum { instance; public void doSomething(){ } }
4 容器方式
这是一个单例的管理类,在程序的初始将许多单例类型存入map中,使用的时候通过key取出。使用统一的接口操作,隐藏了具体实现,降低耦合。
public class Singleton_Manager<T> { private static Map<String,Object>map=new HashMap<>(); private Singleton_Manager(){} public static void registerInstance(String key,Object o){ if(!map.containsKey(key)){ map.put(key,o); } } public static Object getInstance(String key){ return map.get(key); } }
不管方式,核心都是私有构造方法,静态获取唯一。这个过程需要保证线程安全,防止反序列化。具体根据并发环境,资源消耗等选择。
相关文章推荐
- java.io.Serializable浅析
- linux内核启动过程追踪
- jQuery代码优化:事件委托篇
- ASP.Net后台 实现先弹出对话框,再跳转到另一个网页的实现方法
- Dubbo基本原理机制(优秀推荐)
- UVa 12563 - Jin Ge Jin Qu hao(类01背包,白书)
- 初学者教程:第一只爬虫——爬取招聘信息(三)
- 腾讯后台模拟笔试题(附答案)
- 一个基本的web项目(无任何框架)
- iOS非CocoPods集成七牛推流SDK PLCameraStreamingKit 使用指南
- Binary Search---First Position of Target
- Android实现一个自定义相机的界面
- 从头学习JAVA(五)
- C语言学习笔记 -冒泡排序
- s4:VB之如何更改排序的数字的数量
- CCF201503(1-3)3仅90分,求指点
- AVL树
- 调整磁盘分区(扩展系统盘)
- 求最大公约数的两种算法
- 嵌入式操作系统内存管理有哪几种,各有何特性