什么是“单例模式”
单例模式:确保某个类只有一个实例,并且只能自行实例化并且向系统提供这个示例。
单例模式有几个特点:
- 单例类只能有一个实例
- 单例类必须自己创建自己的实例
- 单例类必须给其他类提供这个实例
单例模式在很多场景都可以使用到,比如线程池、缓存、日志对象、打印机或者显卡驱动的对象等等,这些场景下,如果有多个实例的话,可能会导致程序的行为异常、资源使用过量等问题的出现。因此,学习单例模式是很有必要的。
如何实现单例模式
在单例模式中,主要有两种方式实现单例模式:
- 饿汉式
- 懒汉式
1. 懒汉式
懒汉:顾名思义,很懒!所以这个实例是在第一次调用的时候才会进行实例化。
//懒汉式单例类.在第一次调用的时候实例化自己
public class Singleton {
private Singleton() {}
private static Singleton single=null;
//静态工厂方法
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
从代码中可以看出,实现懒汉式单例类有两个步骤:
- 将构造方法私有化,并将对象设置为静态私有
- 提供一个静态方法供外部程序调用获取实例。
实现了以上两个步骤之后,当外部程序第一个访问这个单例类的时候,Singleton将会进行实例化并返回。
那么,考虑一个问题:多线程下会发生什么问题?
我们可以把自己当做JVM来运行一下这个程序:
线程一进入getInstance 方法,判断 single == null之后,线程二抢夺了CPU的资源,线程一被挂起,线程二也进入 getInstance 方法,此时因为在线程一中还没有对 Singleton 进行实例化,因此线程二判断为 null ,实例化对象后成功返回。
这是执行权交回线程一,在停掉的那个地方继续往下执行,实例化 Singleton 对象,再返回。
扫描二维码关注公众号,回复: 1550322 查看本文章![]()
这样,导致返回了两个不同的对象,破坏了单例模式的原则:单例类只能有一个实例
那么,怎么解决呢?
我们有两种方法解决这个问题:
在getInstance方法上加同步
public static synchronized Singleton getInstance() { if (single == null) { single = new Singleton(); } return single; }
双重检查加锁
private volatile static Singleton single; public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; }
两种方法的区别:
- 第一种方式在方法上加了 synchronized ,虽然保证了线程安全,但是每次调用的时候都要进行同步,降低了性能,因为99%的情况下都是不需要同步的。
- 第二种方法使用了双重检查加锁的方式,只有在第一次实例化的时候才需要同步。这种方法既保证了线程安全,又保证性能没有大幅度下降。
2. 饿汉式
饿汉式,这个方法就比较简单了。看名字我们也可以看出来,饿汉,那么就代表着在未访问之前就将单例类实例化好了。
下面是它的代码实现:
//饿汉式单例类.在类初始化时,已经自行实例化
public class Singleton1 {
private Singleton1() {}
private static final Singleton1 single = new Singleton1();
public static Singleton1 getInstance() {
return single;
}
}
饿汉式在类创建的同时,就已经实例化了一个静态对象,因此是天生线程安全的。
懒汉式和饿汉式的区别
线程安全
饿汉式天生线程安全,可以直接使用在多线程环境下。
懒汉式本身是非线程安全的,要实现线程安全请参考上面代码。
性能
饿汉式在类初始化的时候就会进行实例化单例类。 因此, 在初始化初期会占用较大资源。 但是相应带来的好处就是在第一次调用的时候速度变快。
懒汉式的初始化策略就是 “ 延迟加载 ” , 因此在第一次调用的时候会占用较大资源。而且在保证线程安全的解决方法中也存在不同的性能问题(区别请往上回顾一下)。
单例模式的优缺点
优点:
- 因为只有一个实例,减少了系统性能开支。
- 避免对内存的多重占用。
- 可以在系统中设置全局访问点,优化和共享资源访问。
缺点:
- 拓展困难。(没有接口)
- 对测试不利。
- 与单一原则冲突。一个类负责单一功能 。而单例模式 将单例类的实例化和其他业务逻辑放在同一个类中。
感谢阅读本博客。
欢迎关注我的博客:https://li-weijian.github.io/
欢迎关注我的CSDN:https://blog.csdn.net/qq352642663
需要联系请加QQ:352642663
欢迎联系我共同交流