单例模式的动机
对于系统中的某些类来说,只有一个实例很重要。例如,一个系统中可以存在多个打印任务,但是只有一个正在工作的任务;一个系统中只能有一个窗口管理器或者文件系统;一个系统中只能一个计时工具或者ID(序号程程期。加入在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容是完全一致的,则是重复对象,浪费内存资源。如果这些窗口现实的内容不一致,则意味着在某一瞬间系统有多个状态,与现实不符,也会给用户带来误解,不知道哪一个才是真是的状态,因此有事确保系统中某个对象的唯一性即一个类只能有一个实例很重要。
如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机
★单例模式的要点
·某个类只能有一个实例(类定义中含有一个该类的静态私有对象)
·他必须自行创建这个实例(单例模式的类只提供私有的构造函数)
·他必须自行向整个系统提供这个实例(该类提供一个静态共有的函数用于创建与获取他本身的静态私有对象)
饿汉式单例(立即加载,线程安全)
// 饿汉模式
//java允许我们在内中定义一个静态类
//java中顶级类不用用static关键字
public class SingleTon1 {
//在自己的内部定义一个自己的实例,供自己使用
private static SingleTon1 singleTon1=new SingleTon1();
//构造私有
private SingleTon1() {
}
//为外部提供一个访问本类的静态方法
public static SingleTon1 getInstance() {
return singleTon1;
}
}
饿汉式单例:调用静态方法在类初始化的时候就会创建一个单例供外部使用,因为是在初始化的时候生成单例,在一个JVM范围内对象不会改变,所以不会出现多线程多实例的情况。但是由于生成单例的时机太早,会一直占用内存。
注意:由于构造私有,所以该单例不能被外部实例化。但是通过反射机制是能够获取私有构造的。
懒汉式单例(延迟加载,线程不安全)
public class SingleTon2 {
//在自己的内部定义一个自己的实例,供自己使用
private static SingleTon2 singleTon2;
//构造私有
private SingleTon2() {
}
//获取单例
public static SingleTon2 getInstance() {
if(singleTon2==null) {
return new SingleTon2();
}
return singleTon2;
}
}
懒汉式单例:在调用getInstance()方法触发类初始化,singleTon2在类初始化完成后任为null,只有继续执行方法判断为null时,才会构造单例。
注意:虽然避免了立即加载的缺点,但是在多线程情况下,会产生多个实例。(比如两条线程先后连续判断singleTon2为空,那么两条线程会先后new SingleTon,这样就存在这两个不同的实例了,违反了单例模式的规则。
懒汉同步锁单例(延迟加载,线程安全,性能低下)
public class SingleTon3 {
private static SingleTon3 singleTon3;
private SingleTon3() {
}
public synchronized static SingleTon3 getInstance() {
if(singleTon3==null) {
return new SingleTon3();
}
return singleTon3;
}
}
通过synchronized关键字在静态方法上加锁,由于方法执行是同步的,所以不会有产生多个实例的情况,他的线程是安全的。
注意:由于在整个方法都加了锁,多条线程情况下,都会处于等锁的状态。线程独占处理机,势必造成性能低下
★懒汉双重效验锁单例double-checked locking(延迟加载,线程安全,性能较好)推荐
public class SingleTon4 {
private volatile static SingleTon4 singleTon4;
private SingleTon4() {
}
public static SingleTon4 getInstance() {
if(singleTon4==null) {
synchronized(SingleTon4.class){
if(singleTon4==null) {
return new SingleTon4();
}
}
}
return singleTon4;
}
}
双重效验锁:只在产生实例的代码上加锁,优化了流程。避免了不为空也加锁的情况,提高了效率。
注意:由于指令重排会导致DCL失效,volatile关键字禁止JVM进行指令重排。
★静态内部类 推荐
public class SingleTon5 {
// 私有构造
private SingleTon5() {}
// 静态内部类
private static class InnerObject{
private static SingleTon5 single = new SingleTon5();
}
public static SingleTon5 getInstance() {
return InnerObject.single;
}
}
这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了 classloder 机制来保证初始化 instance 时只有一个线程,它跟第 1 种方式不同的是:第 1 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。
★枚举 推荐
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
·静态内部类
定义一个内部类的时候用static修饰,就是一个静态内部类。只用外部类被访问了才能访问内部类。
设计使用static内部类的主要目的:
1、将与外部类关系密切的类逻辑上组织在一起,并进行访问权限的控制。
2、能访问外部类中所有的成员。外部类的static成员可以直接访问,非static型间接访问,即先创建外部类对象,然后通过该对象访问。