单例模式

单例模式
保证一个类只有一个实例,并提供一个访问它的全局访问点。
 在我们日常的工作中经常需要在应用程序中保持一个唯一的实例,如:IO处理,数据库操作等,由于这些对象都要占用重要的系统资源,所以我们必须限制这些实例的创建或始终使用一个公用的实例


对单例模式讲解较细的一篇文章:
http://www.cnblogs.com/rush/archive/2011/10/30/2229565.html


单例模式如何实现:
1. 首先确保这个类不能通过new被访问者实例化,需要将无参构造函数私有化,private Singleton(){},
2. 要有一个静态私有的成员变量instance与静态公有的instance方法,instance()负责判断当前类是否被创建并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建。
public class Singleton {
   
   private static Singleton sing;//静态成员变量

   private Singleton() {//无参构造函数私有化
       
   }
   //静态方法
   public static Singleton getInstance() {
       if (sing == null) {
           sing = new Singleton();
       }
       return sing;
   }
}


多线程单例模式的实现方式:
1.如果只按照上面的实现方式,可以实现单例模式,但是只能在单线程环境中运行,是线程不安全的,多线程时有可能会同时调用instance(),同时判断当前类==null,造成创建多个实例,不能确保单例了。
2.多线程单例模式的实现:需要在instance()方法的if (sing == null)判断前加一个synchronized,静态成员变量可以放在ConcurrentHashMap集合中,是线程安全的(如果是方法内的局部变量就还是用hashMap,毕竟线程安全的对性能消耗较大),创建了一个静态只读的进程辅助对象,由于synchronized是确保当一个线程位于代码的临界区时,另一个线程不能进入临界区(同步操作)。如果其他线程试图进入锁定的代码,则它将一直等待,直到该对象被释放。从而确保在多线程下不会创建多个对象实例了。只是这种实现方式要进行同步操作,这将是影响系统性能的瓶颈和增加了额外的开销
public class Singleton {
   
   private static Singleton sing;
   //静态只读的进程辅助对象
   private static final lockObject = new Object();
   private Singleton() {
       
   }
   
   public static Singleton getInstance() {
       Synchronized(lockObject){
         if (sing == null) {
             sing = new Singleton();
         }
         return sing;
       }
   }
}
由于同步操作,对系统性能有影响的,可以通过减少线程操作来避免性能浪费,在Synchronized(lockObject)同步操作上再加个if (sing == null)判断。
   public static Singleton getInstance() {
     if (sing == null){//先判断一个null,较少同步操作
       Synchronized(lockObject){//同步锁住线程
         if (sing == null) {//还要进行一次null判断
//当两个线程都进行了第一个null判断后,线程A进入,线程B等线程A执行后也会进入的,如果不再进行一次null判断,就会导致A创建实例后,B又创建了
             sing = new Singleton();
         }
         return sing;
       }
     }
   }
单例模式的优点:
单例模式(Singleton)会控制其实例对象的数量,从而确保访问对象的唯一性。
1. 实例控制:单例模式防止其它对象对自己的实例化,确保所有的对象都访问一个实例。
2. 伸缩性:因为由类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
 
单例模式的缺点:
1. 系统开销。虽然这个系统开销看起来很小,但是每次引用这个类实例的时候都要进行实例是否存在的检查。这个问题可以通过静态实例来解决。
2. 开发混淆。当使用一个单例模式的对象的时候(特别是定义在类库中的),开发人员必须要记住不能使用new关键字来实例化对象。因为开发者看不到在类库中的源代码,所以当他们发现不能实例化一个类的时候会很惊讶。
3. 对象生命周期。单例模式没有提出对象的销毁。在提供内存管理的开发语言(比如,基于.NetFramework的语言)中,只有单例模式对象自己才能将对象实例销毁,因为只有它拥有对实例的引用。在各种开发语言中,比如C++,其它类可以销毁对象实例,但是这么做将导致单例类内部的指针指向不明。
 
单例适用性
使用Singleton模式有一个必要条件:在一个系统要求一个类只有一个实例时才应当使用单例模式。反之,如果一个类可以有几个实例共存,就不要使用单例模式。
不要使用单例模式存取全局变量。这违背了单例模式的用意,最好放到对应类的静态成员中。
不要将数据库连接做成单例,因为一个系统可能会与数据库有多个连接,并且在有连接池的情况下,应当尽可能及时释放连接。Singleton模式由于使用静态成员存储类实例,所以可能会造成资源无法及时释放,带来问题。


延迟初始化单例
在将延迟初始化单例前,先了解下初始化的几种方式
有急切初始化和延迟初始化
急切初始化也就是静态初始化,代码如下:
public class Singleton {
         private Singleton1(){}
         private final static Singleton sInstance=new Singleton();


         public static Singleton getlnstance(){
            return sInstance;
         }
}
Singleton 类是什么时候初始化化的呢,由于无参构造函数私有化后,我们是不能通过new创建实例的,只能通过调用getlnstance()方法来创建Singleton 实例,但是sInstance是静态常量,所以在调用getlnstance()方法时,Singleton 就已经初始化了。


再来看看延迟初始化的代码:
public class Singleton {
         private Singleton(){}
         private static class SingletonHolder {
               final static Singleton holder=new Singleton();


         }
         public static Singleton getlnstance(){
            return SingletonHolder.holder;
         }
}
创建一个静态内部类,当调用getlnstance()方法时,SingletonHolder.holder触发内部类,内部类再进行Singleton 的初始化,
//通过该方法调用内部类,实现延迟初始化单例模式,由于JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。
//这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕


两种初始化比较:
Java 中类的加载本身是“延迟”的,当你第一次在代码中提到 Singleton 这个类的时候,它才被加载,内部类也是一样的。


换句话说,对于第一种,假如你不调用  Singleton.getInstance(); 而是写:


Class<?> singletonType = Singleton.class;


那 sInstance 也会被建立。




而 Singleton.SingletonHolder 则不同,SingletonHolder 是 private 的内部类,提到它的唯一的地方在 getInstance() 方法内,所以只有第一次运行 Singleton.getInstance() 方法的时候它才会被加载,而作为它静态成员的 holder 才会被建立。




静态内部类,优点:加载时不会初始化静态变量INSTANCE,因为没有主动使用,达到Lazy loading懒加载特性


public class WorkExecutorRepository {
//先定义一个线程安全的map集合,用于存放一个线程池实例,已相应的队列为key值
    private final ConcurrentHashMap<String, ThreadPoolExecutor> executors;
//将当前类的的初始化隐藏,不能通过外部调用初始化
    private WorkExecutorRepository() {
        executors = new ConcurrentHashMap<String, ThreadPoolExecutor>();
    }
//通过内部类实现,延迟初始化单例模式的操作(懒加载),只有主动触发这个内部类时才会初始化
//避免了在加载当前类时就会初始化
    /**
     * 延迟初始化单例模式
     */
    private static class SingletonHandler {
        final static WorkExecutorRepository INSTANCE = new WorkExecutorRepository();
    }
//通过该方法调用内部类,实现延迟初始化单例模式,由于JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。
//这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕
    public static WorkExecutorRepository getInstance() {
        return SingletonHandler.INSTANCE;
}


单例的是否懒加载
其实多数情况下一个被设计成单例的类,你第一次提到它的类名一般就是要调用 getInstance() 方法,一般单例的类也只有这一个静态方法,所以第一种就行了。


单例的是否懒加载,也就是单例对象的创建时机的问题.
懒加载, 也就是在不调用getlnstance()方法的时候,Singleton 对象是不创建的.
非懒加载,就是只要主动使用了Singleton ,对象就会被创建.


你的第一个例子,sInstance变量被定义为static类型的,所以第一次主动使用Singleton类的时候,sInstance就会被初始化,也就是单例对建被创建了出来.也即非懒加载.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值