单例模式
什么是单例模式
简单来说,就是用来创建独一无二的,只有一个实例对象,是一种最简单的设计模式。
特点:
1.一个类只能有一个实例;
2.自己创建这个实例;
3.整个系统都要使用这个实例。
为什么需要单例模式
对于有一些对象,其实例我们只需要一个,比方说:线程池、缓存(cache)、日志对象等,如果创建多个实例,就会导致许多问题产生,比如资源使用过量、程序行为不可控,或者导致不一致的结果。
单例模式的基本构造
- 私有的构造方法
- 私有的、静态的实例化变量应用
- 提供一个公有的、静态的获取类实例对象方法
常见的七种单例模式的写法
饿汉式单例模式
/**
* 饿汉式实现单例模式
*/
public class Singleton1 {
private static Singleton1 uniqueInstance = new Singleton1();
private Singleton1() {
}
public static Singleton1 getUniqueInstance() {
return uniqueInstance;
}
}
饿汉式单例模式是最简单也是最粗暴的实现单例的方式,在类加载的时候就已经实现单例模式对象的生成,是一种推荐的写法。但是个人觉得如果出现该单例对象占用内存很大但是从来未被调用的情况,该方式就会造成资源的浪费。在生产环境中还没有遇到这样的情况,存疑!
经典的单例模式(线程不安全)
/**
* 经典的单例模式实现(线程不安全)
*/
public class Singleton2 {
private static Singleton2 uniqueInstance;
private Singleton2() {
}
public static Singleton2 getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton2();
}
return uniqueInstance;
}
}
这种方式在单线程情况下没有问题,属于懒汉式,但是多线程的时候会导致可能创建多个对象,也就破坏了单例模式,不推荐!
懒汉式单例模式(线程安全)
/**
* 懒汉式单例模式(线程安全)
*/
public class Singleton3 {
private static Singleton3 uniqueInstance;
private Singleton3() {
}
public static synchronized Singleton3 getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton3();
}
return uniqueInstance;
}
}
这种方式属于线程安全的懒汉式单例模式。懒汉式的含义就是只有在需要生成单例对象的时候才会被调用生成该对象,灵活性更大。但是该模式可能会造成系统资源的浪费,原因是synchronized加在方法体上,每调用一次方法都会进行加锁,正常是在第一次生成单例对象的时候才需要加锁,后续调用再进行加锁是对系统资源的浪费。不推荐!
双重检查实现懒汉式单例模式
/**
* 懒汉式单例模式(同步代码块双重检查) 此处注意引用需要用volatile修饰,防止指令重排序
*/
public class Singleton4 {
private Singleton4() {
}
private static volatile Singleton4 uniqueInstance;
public static Singleton4 getUniqueInstance() {
if (uniqueInstance == null) {
synchronized (Singleton4.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton4();
}
}
}
return uniqueInstance;
}
}
这种方式是对前一种实现方式的改进,既保证了线程安全,也避免了前一种方式的坑,但是需要注意的是单例对象的引用需要用volatile关键字修饰,防止指令重排序,避免产生未初始化完全的对象。推荐!
另一种懒汉式单例模式
/**
* 懒汉式单例(同步代码块)
*/
public class Singleton5 {
private Singleton5() {
}
private static Singleton5 uniqueInstance;
public static Singleton5 getUniqueInstance() {
if (uniqueInstance == null) {
synchronized (Singleton5.class) {
uniqueInstance = new Singleton5();
}
}
return uniqueInstance;
}
}
这种方式对比双重检查的方式,少了一层检查,结果显然是不能保证只实例化一个对象出来,不推荐!
静态内部类实现单例模式
/**
* 静态内部类实现单例模式
*/
public class Singleton6 {
private Singleton6() {
}
private static class SingletonIns {
private static final Singleton6 INSTANCE = new Singleton6();
}
public static Singleton6 getInstance() {
return SingletonIns.INSTANCE;
}
}
这种方式在我这边的生产环境中用的比较多,代码结构清晰,推荐!
枚举实现单例模式
/**
* 枚举实现单例模式
*/
enum Singleton7 {
INSTANCE;
public void sayHello() {
System.out.println("Hello World");
}
public static void main(String[] args){
Singleton7.INSTANCE.sayHello();
}
}
这种方式利用了枚举类的特性,代码极其简洁,实际的单例对象就是Singleton7.INSTANCE,而且可以避免反射破坏单例模式,推荐!
通过反射破坏单例模式
此处用静态内部类实现单例模式的代码做演示:
/**
* 静态内部类实现单例模式
*/
public class Singleton6 {
private Singleton6() {
}
public void sayHello(){
System.out.println("Hello World");
}
private static class SingletonIns {
private static final Singleton6 INSTANCE = new Singleton6();
}
public static Singleton6 getInstance() {
return SingletonIns.INSTANCE;
}
public static void main(String[] args)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Singleton6 s1 = Singleton6.getInstance();
System.out.println(s1);
s1.sayHello();
/**
* 通过反射破坏单例模式
*/
//反射获得单例类的构造函数
Constructor<Singleton6> constructor = Singleton6.class.getDeclaredConstructor();
//指示反射的对象在使用时取消Java语言访问检查,绕过private Singleton6()
constructor.setAccessible(true);
Singleton6 s2 = constructor.newInstance();
System.out.println(s2);
s2.sayHello();
}
}
运行结果如下:
singleton.Singleton6@6e0be858
Hello World
singleton.Singleton6@61bbe9ba
Hello World
Process finished with exit code 0
可以看见通过反射,在已经生成了一个单例对象的情况下,又生成了一个完全不同的对象,破坏了单例模式。
而利用枚举则可以避免被反射破坏:
/**
* 枚举实现单例模式
*/
enum Singleton7 {
INSTANCE;
public void sayHello() {
System.out.println("Hello World");
}
public static void main(String[] args)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Singleton7.INSTANCE.sayHello();
/**
* 尝试使用反射来创建单例类对象
* 在通过反射创建对象时,会检查该类是否时ENUM修饰,如果是则抛出异常,反射失败
*/
Constructor<Singleton7> constructor = Singleton7.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton7 singleton7 = constructor.newInstance();
singleton7.sayHello();
}
}
运行结果抛出了异常:
Hello World
Exception in thread "main" java.lang.NoSuchMethodException: singleton.Singleton7.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at singleton.Singleton7.main(Singleton7.java:23)
Process finished with exit code 1
单例模式的优点
- 由于单例模式在内存中只有一个实例,减少内存开支,特别是一个对象需要频繁地创建销毁时,而且创建或销毁时性能又无法优化,单例模式就非常明显了
- 由于单例模式只生成一个实例,所以,减少系统的性能开销,当一个对象产生需要比较多的资源时,如读取配置,产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
- 单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作
- 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如,可以设计一个单例类,负责所有数据表的映射处理
单例模式的缺点
- 不适用于变化的对象
- 由于单例模式没有抽象层,所以扩展困难
- 单例类的职责过重,在一定程度上违背了“单一职责原则”
单一职责原则:一个类,应该只有一个职责