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