前言
在本篇博客中,我们将讲述一下单例模式是什么,它的优缺点,还有如何创建一个单例模式
一、什么是单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
有三个要点
- 该类只有一个实例
- 该类自行创建这个实例
- 它必须自行向整个系统提供这个实例
单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例.
这一点在很多场景上都需要. 比如 JDBC 中的 DataSource 实例就只需要一个.
二、单例模式的优缺点
优点:
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
2、避免对资源的多重占用(比如写文件操作)。
缺点:
没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
三、单例模式之饿汉模式(创建 )
代码如下
//单例模式(饿汉模式),只能实例一个,把构造方法设置为私有,且自身线程安全,因为只有读操作
class Singleton{
private static Singleton instence = new Singleton(); //static修饰表示该对象是类对象,即唯一实例
private Singleton(){}
public static Singleton getInstance(){
return instence;
}
}
public class Demo9 {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
}
}
这是饿汉模式的创建:
这个饿汉模式的饿主要体现在该类被加载时就会创建一个对象(而懒汉模式不会(用到才实例化一个)),但是懒汉模式也有它的优点,即代码简单,不会有线程安全问题,可以保证实例的唯一性,不过它的缺点就是类加载的时候就创建对象,如果系统不需要使用该单例对象,又由于类加载时就会创建,所以有可能会造成资源利用效率不行,而懒汉模式就不会出现这种
四、单例模式之懒汉模式
代码如下
class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这是懒汉模式的创建(单线程)
从上面代码,我们可以看到,在类加载的时候并不会立即创建实例,只有第一次使用该类的时候才会创建对象,无需占用资源,比饿汉模式资源利用率更好,但是因为这个好处也诞生了线程安全问题,比如上述代码,只能适用于单线程,如果在多线程下,那么就会出现线程安全问题了
例如:对象还没创建,当两个线程同时都进入了if判断里,岂不是创建了两个
解决代码如下
class Singleton {
private static Singleton instance = null;
private Singleton() {}
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
我们只需要对getInstance()方法加锁即可解决,这样就可以解决了,保证一次只有一个线程能进入方法。解决了线程安全问题,但是呢,我们可以想想,该代码是否还能优化吗?
从该模式来说,当第一个实例化创建之后,其他就不会再创建,而是返回该单例,即再也进不了if,可是每次方法都加锁了,影响了性能,(所以有了优化方向,只需要对if加锁)
代码如下
//懒汉模式,需要几个就创建几个,不着急,最开始不初始化实例,用到才初始化,
class Singleton2{
private static volatile Singleton2 instence = null; //volatile 禁止指令重排序
private Singleton2(){
}
public static Singleton2 getInstance(){
if(instence == null) { //判断是否需要加锁
synchronized (Singleton2.class) { //涉及读和修改操作,需要加锁才线程安全 类对象(只有一个)
if (instence == null) { //判断是否要修改 (读)
instence = new Singleton2(); // (修改) 这里会出现指令重排序问题(加volatile) 1.创建内存2.赋予空间(属性赋值等)3.付给引用
}
}
}
return instence;
}
}
上述代码,我们可以看出有两个if判断,这也就是懒汉模式单例模式中的双重if校验,接下来讲讲这两个if的作用,第一层if它的目的是优化,毕竟我们只需要实例化一次,用来判断是否需要加锁,比如当多线程情况下,有两个线程同时判断是否为空,都进入了,这时候就需要竞争锁了,线程1进入了,然后线程1成功实例化,释放锁,当线程2再进入发现已经实例化了,就进入不了if了。然后以后就不需要加锁了,因为再也进不了第一层if了(优化(达到只加锁一次))
在上述代码还有一个重点,即 instence 使用volatile修饰,这个volatile修饰是为了防止在实例化对象的时候禁止指令重排序,正如上述代码中注释,当顺序为1->3->2;假如在3->2中间,有其他线程使用该对象就会出现问题,因为对象还没有实例成功。(是否也是为了保证内存可见性存疑)
对比上述两种单例模式,我们发现饿汉模式会出现实例时机太早,万一用不到,则会造成资源利用率下降。懒汉模式虽然解决了资源利用效率,但是必须处理多个线程访问的问题,需要通过双重检查锁定等机制进行控制,导致系统性能受到一定影响。有没有方法综合二者的有点呢?当然有,下面将为大家介绍
五、单例模式之懒汉模式(用静态内部类)
这种方法被称之为Initialization on Demand Holder (IoDH)的技术。
在IoDH中,我们在单例类中增加一个静态(static)内部类,在该内部类中创建单例对象,再将该单例对象通过getInstance()方法返回给外部使用,实现代码如下所示:
//静态内部类实现的单例模式(懒汉)
class Singleton{
private Singleton(){
}
private static class SingletonHolder {
private static final Singleton SINGLETON = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.SINGLETON;
}
}
public class Main {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
}
}
通过使用IoDH,我们既可以实现延迟加载,又可以保证线程安全,不影响系统性能,不失为一种最好的Java语言单例模式实现方式(其缺点是与编程语言本身的特性相关,很多面向对象语言不支持IoDH)。