单例(Singleton)类

如果一个类始终只能创建一个实例,则这个类被称为单例类

在一些特殊场景下,要求不允许自由创建该类的对象,而只允许为该类创建一个对象。为了避免其他类自由创建该类的实例,应该把该类的构造器使用private修饰,从而把该类的所有构造器隐藏起来

根据良好封装的原则:一旦把该类的构造器隐藏起来,就需要一个public方法作为该类的访问点,用于创建该类的对象,且该方法必须使用static修饰(因为调用该方法之前还不存在对象,因此调用该方法的不可能是对象,只能是类)

除此之外,该类还必须缓存已经创建的对象,否则该类无法知道是否曾经创建过对象,也就无法保证只创建了一个对象。为此该类需要使用一个成员变量来保存曾经创建的对象,因为该成员变量需要被上面的静态方法访问,故该成员变量必须使用static修饰

class Singleton
{
    // 使用一个类变量来缓存曾经创建的实例
    private static Singleton instance;
    // 将构造器使用private修饰,隐藏该构造器
    private Singleton(){}
    // 提供一个静态方法,用于返回Singleton实例
    // 该方法可以加入自定义的控制,保证只产生一个Singleton对象
    public static Singleton getInstance()
    {
        // 如果instance为null,表明还不曾创建Singleton对象
        // 如果instance不为null,则表明已经创建了Singleton对象,
        // 将不会重新创建新的实例
        if (instance == null)
        {
            // 创建一个Singleton对象,并将其缓存起来
            instance = new Singleton();
        }
        return instance;
    }
}
public class SingletonTest
{
    public static void main(String[] args)
    {
        // 创建Singleton对象不能通过构造器,
        // 只能通过getInstance方法来得到实例
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2); // 将输出true
    }
}

Coding

代码一:

public class Singleton {  
    private Singleton() {}                     // 关键点0:构造函数是私有的
    private static Singleton single = null;    // 关键点1:声明单例对象是静态的
    public static Singleton GetInstance()      // 通过静态方法来构造对象
    {                        
         if (single == null)
         {                                     // 关键点2:判断单例对象是否已经被构造
             single = new Singleton();  
         }    
        return single;  
    }  
}

代码一是线程不安全的,遇到多线程的并发条件下,有可能给new出多个单例实例

========================================================================================

代码二:

public class Singleton {  
    private Singleton() {}                     // 关键点0:构造函数是私有的
    private static Singleton single = null;    // 关键点1:声明单例对象是静态的
    private static Object obj= new Object();
    public static Singleton GetInstance()      // 通过静态方法来构造对象
    {                        
         if (single == null)                   // 关键点2:判断单例对象是否已经被构造
         {                            
            lock(obj)                          // 关键点3:加线程锁
            {
               single = new Singleton();  
             }
         }    
        return single;  
    }  
}

在关键点2,检测单例是否被构造。虽然这里判断了一次,但是由于某些情况下,可能有延迟加载或者缓存的原因,只有关键点2这一次判断,仍然不能保证系统是否只创建了一个单例,也可能出现多个实例的情况

========================================================================================

代码三:

public class Singleton {  
    private Singleton() {}                     // 关键点0:构造函数是私有的
    private static Singleton single = null;    // 关键点1:声明单例对象是静态的
    private static object obj= new object();
    public static Singleton GetInstance()      // 通过静态方法来构造对象
    {                        
         if (single == null)                   // 关键点2:判断单例对象是否已经被构造
         {                            
            lock(obj)                          // 关键点3:加线程锁
            {
               if(single == null)              // 关键点4:二次判断单例是否已经被构造
               {
                  single = new Singleton();  
                }
             }
         }    
        return single;  
    }  
}

在判断单例实例是否被构造时,需要检测两次,在线程锁之前判断一次,在线程锁之后判断一次,再去构造实例

单例模式关键点

单例是为了保证系统中只有一个实例,其关键点有:

  • 私有构造函数

  • 声明静态单例对象

  • 构造单例对象之前要加锁(lock一个静态的object对象)

  • 需要两次检测单例实例是否已经被构造,分别在锁之前和锁之后

单例模式问答

为何要检测两次?

如上面所述,有可能延迟加载或者缓存原因,造成构造多个实例,违反了单例的初衷

构造函数能否公有化?

不行,单例类的构造函数必须私有化,单例类不能被实例化,单例实例只能静态调用

lock住的对象为什么要是object对象,可以是int吗?

不行,锁住的必须是个引用类型。如果锁值类型,每个不同的线程在声明的时候值类型变量的地址都不一样,那么上个线程锁住的东西下个线程进来会认为根本没锁,相当于每次都锁了不同的门。引用类型的变量地址是相同的,每个线程进来判断锁都想是否被锁的时候都是判断同一个地址,相当于是锁在通一扇门,起到了锁的作用


布still
461 声望32 粉丝

数据挖掘、用户行为研究、用户画像