设计模式之单例模式(一)

单例模式:一个类仅由一个实例,并且提供它的一个全局访问点。
用途:对于一些情况存在多个实例就会可能造成混乱,比如 线程池、打印机、对话框等对象。
要点有三个:
第一个点:一个类只有一个实例
第二个点:它必须自行创建这个实例
第三个点:它必须向整个系统提供实例
具体来说是:
只提供一个该类的私有的构造函数
定义一个该类的私有静态对象
提供一个静态的公有函数来获得该实例对象
单例模式演化之路:
ps:饿汉式(非延时加载)、懒汉式(延时加载)
单线程环境

/*
 * 构造方法定义为私有,即只有类本身内部可以实例化对象
 * 采用延迟加载模式:即只有需要用到单例对象的时候才会创建实例化对象
 * 区别于类加载就实例化对象的情况
 * 但是在多线程下是不安全的:在多线程情况下,执行if(singleInstance==null)
 * 时,多个线程判断都是为空,则会创建多个实例对象。
 * 懒汉式  不安全写法
 */
public class SingleThread {

    private SingleThread() {

    }
    private static SingleThread singleInstance=null;
    public static SingleThread getInstance(){
        if(singleInstance==null){
            singleInstance=new SingleThread();
        }
        return singleInstance;
    }

}

多线程环境

/**
 * 构造方法私有
 * 提供一个对外获得实例接口
 * 也是采用延迟加载 相对于上一个版本 这个是线程安全的
 * 缺点:每次获得实例的时候都会试图添加一个同步锁,而加锁是一个非常耗时的工作,因此方法效率不高
 */
public class SingleThreadLock {
  private SingleThreadLock(){}
  private static SingleThreadLock singleInstance;
  public static SingleThreadLock getInstance(){
      synchronized(SingleThreadLock.class){
          if(singleInstance==null){
              singleInstance=new SingleThreadLock();
          }
      }
      return singleInstance;
  }
}

进化版(仍然不完美)

/**
 * 加锁前后双重检测,可以避免一开始获得对象就试图加锁,
 * 里面的锁也是必须有的,因为多个线程在一个检测时,都为null,当其中一个进入后,创建一个实例对象后释放锁
 * 第二个进程认为对象为null 开始加锁,如果第二个不做判断,则会直接又创建一个对象,所以第二次还要检测。
 * 使用volatile作用:防止指令重排  可见性(当值被改变时,其他线程可以看见)
 */
public class SingleThreadLockAfter {
  private SingleThreadLockAfter(){}
  private static volatile SingleThreadLockAfter singleInstance=null;
  public static SingleThreadLockAfter getInstance(){
      if(singleInstance==null){
          synchronized(SingleThreadLockAfter.class){
              if(singleInstance==null){
                  singleInstance=new SingleThreadLockAfter();
              }
          }
      }
      return singleInstance;
  }
}

其实最后发现通过加锁这条机制并不能称为完美的方案,因为加锁是非常耗时的工作,并且真正加锁并创建对象实例的情况很少。因此可以想想使用Static 来用静态方法或静态变量只初始化执行一次的特性来实现

/**
 * 在类加载的时候就实例化对象,且只会执行一次,由于静态变量是全局变量,不会有拷贝,全局独有,因此属于线程安全的
 * 但是缺点:类的实例化属于饿汉式,类加载时自动创建,不能实现懒加载,因此可能会浪费内存空间
 * 对一些其它情况也可能会不适用,比如 如果创建对象需要读取一些配置参数,在这种方式就不好
 */
public class SingleInstanceStatic {
   private SingleInstanceStatic(){}
   private static SingleInstanceStatic singleInstance=new SingleInstanceStatic();
   public static SingleInstanceStatic getInstance(){
       return singleInstance;
   }
}

使用静态属性虽然可以实现线程安全,也算比较好的方法。
但是还有一个更好的方法:内部静态类来实现延迟加载和线程安全

/**
 *使用静态内部类是为了解决静态属性不能实现懒加载模式,由于使用静态变量在类加载的时候就初始化,所以会造成资源浪费 
 * 使用静态内部类实现是因为如果只是实例化外部类,是不会加载内部类的,除非在下面方法中调用getInstance方法
 * 至于为什么使用静态内部类而不是使用内部类是因为使用内部类则是无法定义静态变量或方法,而外部类静态方法也就无从调用
 * 可以做一个测试来测试内部类的加载顺序
 */
public class SingleInstanceStaticInnerClass {
   private SingleInstanceStaticInnerClass(){}
   private static class SingletonHolder{
       private static final SingleInstanceStaticInnerClass singleInstance=new SingleInstanceStaticInnerClass();

   }
   public static final SingleInstanceStaticInnerClass getInstance(){
       return SingletonHolder.singleInstance;
   }
}

测试内部类的加载顺序

public class InnerClassLoaderTest {
    static{
        System.out.println("out Class Loader");
    }
     private static class InnerClass{
        static{
            System.out.println("inner class Loader");
        }
        private static void method(){
            System.out.println("inner Class static method");
        }
    }
    public static void main(String[] args) {
        InnerClassLoaderTest outClass=new InnerClassLoaderTest();
        System.out.println("-----------------------");
        InnerClassLoaderTest.InnerClass.method();
    }

}

输出结果:
out Class Loader
-----------------------
inner class Loader
inner Class static method

可以看出如果只是创建外部类,没有调用内部类的对象,则是不会加载内部类的,即内部类只有使用的时候才会被加载,因此可以实现延迟加载
枚举实现单例模式

/**
 * 在枚举中我们明确了构造方法限制为私有,在我们访问枚举实例时会执行构造方法,同时每个枚举实例都是static final类型的,也就表明只能被实例化一次。
 * 在调用构造方法时,我们的单例被实例化。 
 * 因为enum中的实例被保证只会被实例化一次,所以我们的INSTANCE也被保证实例化一次
 *  自由序列化,线程安全,保证单例
 */
class Resource{

}
public enum SingleTon {
 INSTANCE;
 private Resource instance;
 //构造器私有 访问时执行就会实例化对象且只有一次,因此是线程安全的
 SingleTon(){
     instance=new Resource();
 }
 public Resource getInstance(){
     return instance;
 }
 public static void main(String[] args) {
     SingleTon.INSTANCE.getInstance();
}
}

private的构造器
Java在序列化和反序列化枚举时做了特殊的规定,枚举的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法是被编译器禁用的,因此也不存在实现序列化接口后调用readObject会破坏单例的问题。
对于线程安全方面,类似于普通的饿汉模式,通过在第一次调用时的静态初始化创建的对象是线程安全的。

猜你喜欢

转载自blog.csdn.net/qq_26564827/article/details/80182724