Java单例模式简单实现

单例模式在作为最简单最常见的设计模式,在面试及实际的开发中都会被问到或者使用,我们先简单的了解一下单例模式的定义及单例模式的优点。

单例模式的定义?
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

为什么使用单例模式?
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
2、避免对资源的多重占用(比如写文件操作)。
3、省去了new操作符,降低了系统内存的使用频率,减轻GC压力

单例模式的几种实现方式

/**
 * 饿汉式单例
 * 
 * 是否线程安全:是
 * 是否延迟加载:否
 * 描述:这种方式比较常用,但容易产生垃圾对象。
 * 优点:没有加锁,执行效率会提高。
 * 缺点:类加载时就初始化,浪费内存。
 * 它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
 * 是否推荐:不需要延迟加载时,推荐使用
 */
public class Singleton {
    private static Singleton singleton = new Singleton();

    public static Singleton getInstance() {
        return singleton;
    }

    private Singleton() {
    }
}
import org.springframework.util.ObjectUtils;

/**
 * 懒汉式,线程不安全
 * 
 * 是否线程安全:否
 * 是否延迟加载:是
 * 描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作
 * 是否推荐:不推荐使用,线程不安全
 */
public class Singleton {
    private static Singleton singleton;

    public static Singleton getInstance() {
        if (ObjectUtils.isEmpty(singleton)) {
            singleton = new Singleton();
        }
        return singleton;
    }

    private Singleton() {
    }
}
import org.springframework.util.ObjectUtils;

/**
 * 懒汉式,线程安全
 * 
 * 是否线程安全:是
 * 是否延迟加载:是
 * 这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
 * 优点:第一次调用才初始化,避免内存浪费。
 * 缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
 * getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。
 * 是否推荐:不推荐使用,synchronized锁住整个getInstance()方法,锁粒度太大效率低下
 */
public class Singleton {
    private static Singleton singleton;

    public static synchronized Singleton getInstance() {
        if (ObjectUtils.isEmpty(singleton)) {
            singleton = new Singleton();
        }
        return singleton;
    }

    private Singleton() {
    }
}
import org.springframework.util.ObjectUtils;

/**
 * 双检锁/双重校验锁(DCL,即 double-checked locking)
 * 
 * 是否线程安全:是
 * 是否延迟加载:是
 * 这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
 * getInstance() 的性能对应用程序很关键。
 * 是否推荐:不推荐使用
 * 不推荐使用原因:在Java指令中创建对象和赋值操作是分开进行的,也就是说singleton = new Singleton();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给singleton成员,然后再去初始化这个Singleton实例。
 * 以A、B两个线程为例:
 * 1、A、B线程同时进入了第一个if判断;
 * 2、A首先进入synchronized块,由于singleton为null,所以它执行singleton= new Singleton();
 * 3、由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给singleton成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块
 * 4、B进入synchronized块,由于singleton此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序
 * 5、此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了
 * 这样也就引入了下面的实现方式,使用volatile方式防止JVM重排序
 */
public class Singleton {
    private static Singleton singleton;

    public static Singleton getInstance() {
        if (ObjectUtils.isEmpty(singleton)) {
            synchronized (Singleton.class) {
                if (ObjectUtils.isEmpty(singleton)) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

    private Singleton() {
    }
}
import org.springframework.util.ObjectUtils;

/**
 * 双检锁/双重校验锁(DCL,即 double-checked locking)volatile 防止重排序
 * 
 * 是否线程安全:是
 * 是否延迟加载:是
 * 通过volatile修饰的变量,不会被线程本地缓存,所有线程对该对象的读写都会第一时间同步到主内存,从而保证多个线程间该对象的准确性
 * 是否推荐:推荐使用,不过volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行效率并不是很高
 */
public class Singleton {
    private static volatile Singleton singleton;

    public static Singleton getInstance() {
        if (ObjectUtils.isEmpty(singleton)) {
            synchronized (Singleton.class) {
                if (ObjectUtils.isEmpty(singleton)) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

    private Singleton() {
    }
}
/**
 * 登记式/静态内部类
 * 
 * 是否线程安全:是
 * 是否延迟加载:是
 * 描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
 * 这种方式同样利用了 classloader 机制来保证初始化 singleton实例时只有一个线程,它跟饿汉式不同的是:饿汉式只要 Singleton 类被装载了,那么 singleton实例就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,singleton实例不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化singleton实例。想象一下,如果实例化 singleton很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 singleton显然是不合适的。这个时候,这种方式相比饿汉式就显得很合理。
 * 是否推荐:推荐使用,使用内部类来维护单例的实现,JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的
 */
public class Singleton {
    public static Singleton getInstance() {
        return SingletonHolder.singleton;
    }

    private static class SingletonHolder {
        private static final Singleton singleton = new Singleton();
    }

    private Singleton() {
    }
}
import org.springframework.util.ObjectUtils;

/**
 * ThreadLocal实现单例
 * 
 * 是否线程安全:是
 * 是否延迟加载:是
 * 线程局部变量,是一种多线程间并发访问变量的一种解决方案,与其synchronized加锁的方式不同,它完全不提供锁,而使用以空间换时间的手段,为每个线程提供变量的独立副本,以保障线程安全
 * 是否推荐:不推荐使用
 */
public class Singleton {
    private static final ThreadLocal<Singleton> threadLocal = new ThreadLocal<>();
    private static Singleton singleton;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (ObjectUtils.isEmpty(threadLocal.get())) {
            synchronized (Singleton.class) {
                if (ObjectUtils.isEmpty(singleton)) {
                    singleton = new Singleton();
                }
            }
            threadLocal.set(singleton);
        }
        return threadLocal.get();
    }
}
/**
 * 枚举实现单例
 * 
 * 描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。不能通过 reflection attack 来调用私有构造方法
 * 是否推荐:推荐使用,枚举来实现单实例控制会更加简洁,而且JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式
 */
public enum Singleton {
    INSTANCE;

    public void whateverMethod() {
    }

}
/**
 * 枚举内部类实现单例
 * 
 * 描述:内部类使用枚举实现单例获取
 * 是否推荐:推荐使用
 */
public enum Singleton {
    private Singleton() {
    }

    private enum SingletonHolder {
        INSTANCE;

        private Singleton instance;

        SingletonHolder() {
            this.instance = new Singleton();
        }

        private Singleton getInstance() {
            return instance;
        }
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE.getInstance();
    }
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值