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