设计模式之单例模式
单例模式是设计模式中,关于生产对象类型的设计模式的一种!
单例模式的具体表现是不可以获取同一个类的多个对象,反复获取也只会得到同一个对象!
假设需要将King
类设计为单例的,首先,普通的类的代码例如:
public class King {
}
这样的类是可以随意创建对象的,例如:
King k1 = new King();
King k2 = new King();
King k3 = new King();
显然,随意创建对象是违背了单例模式的设计思想的,所以,为了避免随意对象,可以:
public class King {
private King() {
// 将构造方法私有,使之不可以在类的外部来创建对象
}
}
当然,私有化构造方法的目的并不是“不允许创建对象”,而是“不允许随意创建对象”,为了保证在类的外部依然可以获取当前类的对象,还可以在类中添加获取对象的方法:
public class King {
private King() {
// 将构造方法私有,使之不可以在类的外部来创建对象
}
public King getInstance() {
return new King();
}
}
则外部通过调用getInstance()
方法仍可以获取对象,但是,每调用一次,都会创建一个新的对象,所以,还需要进一步调整:
public class King {
private King king = new King();
private King() {
// 将构造方法私有,使之不可以在类的外部来创建对象
}
public King getInstance() {
return king; // 直接返回私有的全局属性
}
}
由于在初始化时,private King king = new King();
只会执行1次,反复调用getInstance()
方法,获取到的都是同一个对象!这就符合了单例模式的设计思想!
但是,在以上代码中,如果没有King
的对象,是无法调用getInstance()
方法,而获取对象的唯一途径就是要调用这个方法!所以,为了解决这个矛盾,需要使用static
修饰这个方法:
public class King {
private King king = new King();
private King() {
// 将构造方法私有,使之不可以在类的外部来创建对象
}
public static King getInstance() {
return king; // 直接返回私有的全局属性
}
}
一旦在getInstance()
方法的声明中添加了static
关键字,还需要在private King king = new King();
这个属性的声明中也补充static
关键字,因为“被static
修饰的成员不可以直接访问没被static
修饰的成员”,所以,代码还需要改成:
public class King {
private static King king = new King();
private King() {
// 将构造方法私有,使之不可以在类的外部来创建对象
}
public static King getInstance() {
return king; // 直接返回私有的全局属性
}
}
至此,单例模式就设计完成!
以上单例的代码是“饿汉式单例模式”,另外,还有“懒汉式单例模式”,其特征是“不到逼不得已,不创建对象”!例如:
public class King {
private static King king;
private King() {
// 将构造方法私有,使之不可以在类的外部来创建对象
}
public static King getInstance() {
// 判断全局的king是否被创建了对象,如果没有,则创建
if (king == null) {
king = new King();
}
return king;
}
}
但是,以上代码是存在线程安全风险的!当存在多个“同时”运行的线程时,是可能创建出多个对象的!为了解决线程安全问题,需要:
public class King {
private static King king;
private static final Object lock = new Object();
private King() {
// 将构造方法私有,使之不可以在类的外部来创建对象
}
public static King getInstance() {
// 为了解决线程安全问题,需要锁上以下代码
synchronized(lock) {
// 判断全局的king是否被创建了对象,如果没有,则创建
if (king == null) {
king = new King();
}
}
return king;
}
}
但是,一旦加了锁,效率又会变得比较低下!因为每次调用以上方法都需要锁上代码才可以继续向后执行,在多线程的应用场景中,多个“同时”运行的线程在执行这段代码是互斥的,为了解决效率偏低的问题,还会:
public class King {
private static King king;
private static final Object lock = new Object();
private King() {
// 将构造方法私有,使之不可以在类的外部来创建对象
}
public static King getInstance() {
// 判断是否有必要锁住代码
if (king == null) {
// 为了解决线程安全问题,需要锁上以下代码
synchronized(lock) {
// 判断全局的king是否被创建了对象,如果没有,则创建
if (king == null) {
king = new King();
}
}
}
return king;
}
}
至此,懒汉式的单例模式也就完成了!
总的来说,饿汉式的特点是:刚刚加载时就创建了对象,而懒汉式的特点是:当第1次尝试获取对象时才创建对象!