开始学习Java设计模式时第一个学习的模式是单例模式,参考书籍为《设计模式之禅》第2版,在此做一个记录以及学习心得,便于以后进行回顾
提示:以下是本篇文章正文内容,下面案例可供参考
在古代大家谈论的时候只要提及皇帝,每个人都知道指的是谁,不需要在皇帝前面加上特定的称呼。这个过程反映到设计领域就是要求一个类只能生成一个皇帝,所有的对象对它的依赖都是相同的。接下来将这个场景用程序来实现
public class Emperor {
// 在皇帝类内部初始化一个皇帝
private static final Emperor emperor = new Emperor();
// 设置构造方法为private防止其它类再生成另一个皇帝
private Emperor(){
}
// 定义外部获取到该类对象的唯一方法
public static Emperor getInstance(){
return emperor;
}
// 皇帝开始说话了
public static void say(){
System.out.println("我就是皇帝XXX");
}
}
通过定义一个私有访问权限的构造函数,避免被其他类new出来一个对象,而Emperor自己则可以new出一个对象来,其他类对该类的访问都可以通过getInstance()获得同一个对象。
public class Minister {
public static void main(String[] args) {
// 模拟一个上朝的场景,臣子连续三天上朝叩拜皇帝
for (int day = 0;day < 3;day++){
// 记录臣子第几天上朝叩拜皇帝
System.out.println("<--------第" + (day + 1) + "天上朝-------->");
// 臣子叩拜皇帝时说的话
System.out.println("吾皇万岁万岁万万岁");
// 开始获取到皇帝对象
Emperor emperor = Emperor.getInstance();
// 皇帝开始说话了
emperor.say();
}
}
}
单例模式:
Ensure a class has only one instance, and provide a global point of access to it.
(确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例)
单例模式属于创造类型设计模式
Single类为单例类,通过使用private的构造函数确保了在某一个应用中只产生一个实例,(构造方法为private时无法在其他类中通过new关键字实例该类的对象),并且在Singleton类中自行完成实例化
自行实例化代码:
private static final Singleton singleton = new Singleton();
public class Singleton {
// 在类的内部将本类的对象自行实例化
private static final Singleton singleton = new Singleton();
// 将构造方法访问权限设置为private来限制产生多个对象
private Singleton(){
}
// 外界只能通过该方法获得实例对象
public static Singleton getSingleton(){
return singleton;
}
// 本类中的其它方法,尽量为static
public static void doSomething(){
}
}
通常在一个系统中,要求某些类有且仅有一个对象。如果出现多个对象就会出现一些不必要的问题时可以采用单例模式
在非高并发(低并发)的情况下,单例模式不会出现产生多个实例的问题。但是在高并发的情况下,使用单例模式就需要考虑进程同步的问题。
例如下列代码:
public class Singleton {
// 在类的内部将本类的对象自行实例化
private static Singleton singleton = new Singleton();
// 将构造方法访问权限设置为private来限制产生多个对象
private Singleton(){
}
// 外界只能通过该方法获得实例对象
public static Singleton getSingleton(){
if (singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
在这段单例模式的代码中,如果在高并发的情况下会出现危险。
if (singleton == null){
singleton = new Singleton();
}
原因:
在高并发的场景下,如果一个线程A执行到singleton = new Singleton(),但是此时还没有获取到对象(对象的初始化是需要时间的),假设在这同一时间第二个线程B执行到了(singleton == null)判断,那么线程B获得的判断条件也是真,也是单例对象还不存在,于是继续运行下去,线程A和线程B都获得了一个对象,在内存中就出现了两个对象。
因此为了解决这种线程不安全的情况,可以在getSingleton方法前加synchronized关键字,也可以在getSingleton方法内部增加synchronized关键字来实现。
所以就引出了多线程安全的两种单例模式:懒汉式单例模式和饿汉式单例模式
public class Singleton {
// 在类的内部将本类的对象自行实例化
private static Singleton singleton = new Singleton();
// 将构造方法访问权限设置为private来限制产生多个对象
private Singleton(){
}
// 外界只能通过该方法获得实例对象
public static synchronized Singleton getSingleton(){
if (singleton == null){
singleton = new Singleton();
}
return singleton;
}
// 本类中的其它方法,尽量为static
public static void doSomething(){
}
}
正如之前提到过解决多线程的方法,多线程安全的懒汉式单例模式通过加锁有效的避免了在内存中产生多个对象的问题,所以这种懒汉式单例模式的线程是安全的,并且第一次调用时才初始化,很好的避免了内存浪费。但是懒汉式单例模式必须加锁synchronized才能保证单例,但加锁会影响效率。
public class Singleton {
// 在类的内部将本类的对象自行实例化
private static Singleton singleton = new Singleton();
// 将构造方法访问权限设置为private来限制产生多个对象
private Singleton(){
}
// 外界只能通过该方法获得实例对象
public static Singleton getSingleton(){
return singleton;
}
// 本类中的其它方法,尽量为static
public static void doSomething(){
}
}
饿汉式单例模式在类加载时就初始化,会浪费内存。但是相反地,因为饿汉式单例模式没有加锁,执行效率会提高。
优缺点\模式 | 懒汉式单例模式 | 饿汉式单例模式 |
---|---|---|
优点 | 第一次调用才初始化,避免内存浪费 | 没有加锁,执行效率会提高 |
缺点 | 必须加锁 synchronized 才能保证单例,但加锁会影响效率 | 类加载时就初始化,浪费内存 |
多线程安全 | 安全 | 安全 |
以上便是Java设计模式中的第一个单例模式,单例模式是23个模式中比较简单的模式,应用也特别广泛。例如在Spring中,每个Bean默认就是单例的,这样做得有点就是Spring容器可以管理这些Bean的声明周期,可以自由的决定什么时候将Bean创建出来,什么时候销毁Bean、销毁的时候要如何处理等。