设计模式之单例模式
核心作用:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
常见应用场景:
- 项目中读取配置文件的类,一般也只有一个对象。没必要每次使用配置文件的时候,都new一个对象去读取;
- 数据库连接池的设计一般是单利模式,因为数据库的连接是很耗资源的;
- 在servlet中的Application也是单利模式;
- 在Spring中,每个Bean都是单利的,这样的优点就是Spring容器可以去管理;
- 在Servet编程中,每个servlet也是单例的;
- springMVC中的控制器也是单例的;
单例模式的优点:
- 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象产生需要消耗较多资源时,如读取配置文件、产生其他依赖对象时,则可以通过在启动时直接创建出一个对象,然后永久驻留内存的方式来解决
- 单例模式可以在系统设置全局的访问点,优化环共享资源的访问
常见的四种单例模式的实现方式:
-饿汉式(线程安全,调用效率高,不能延时加载)
-懒汉式(线程安全,调用效率不高,可延时加载)
-静态内部类式(线程安全,调用效率高,可延时加载)
-枚举单例(线程安全,调用效率高,不能延时加载,并且可以避免通过反射和序列化创建新对象)
选用方式:
单例对象占用资源少,不需要延时加载
枚举式 好于 饿汉式;
单例对象占用资源大,需要延时加载
静态内部类式 好于 懒汉式;
饿汉式
实现类:
/** * <p> * SingletonTest01 单例模式测试类:饿汉式 * <p>*/ public class SingletonTest01 { //类初始化装载的时候,初始化该对象(类初始化是天然的线程安全) private static SingletonTest01 instance = new SingletonTest01(); // 私有的构造器 private SingletonTest01() { } // 方法没有同步块,并发效率高 public static SingletonTest01 getInstance() { return instance; } }
测试类:
public class Client { public static void main(String[] args) { SingletonTest01 s1 = SingletonTest01.getInstance(); SingletonTest01 s2 = SingletonTest01.getInstance(); System.out.println(s1); System.out.println(s2); } }
测试结果:
说明:
饿汉式单例模式中,static变量会在类装载的时候初始化,此时也不会涉及多线程的访问问题,即虚拟机只会装载一次该类,肯定不会发生多线程访问问题,因此可以省略synchronized关键字。
缺点:
如果加载了该类,但是在程序中没有调用getInstance(),或者永远没有调用,会造成资源浪费。
懒汉式:
测试代码:
/** * <p> * SingletonTest02 单例模式:懒汉式 * <p>*/ public class SingletonTest02 { //类初始化装载的时候,不初始化该对象(延时加载,真正使用的时候再创建) private static SingletonTest02 instance; private SingletonTest02() { } //方法需同步保证线程安全,调用效率低 public static synchronized SingletonTest02 getInstance() { if (instance != null) { return instance; } else { return new SingletonTest02(); } } }
测试类及测试结果同上!
说明:实现了懒加载!
缺点:由于每次都要同步,并发效率低!
静态内部类实现方式:
测试类:
/** * <p> * SingletonTest03 静态内部类实现单例模式 * <p>*/ public class SingletonTest03 { private static class SingletonInstance { private static final SingletonTest03 instance = new SingletonTest03(); } // 方法没有同步,调用效率高 public static SingletonTest03 getInstance() { return SingletonInstance.instance; } private SingletonTest03() { } }
说明:外部类没有static属性,则不会像饿汉式立即加载对象;只有在真正调用getInstance()才会加载静态内部类。由于加载类是线程安全的,instance是static final 类型,保证内存中只有一个实例存在,而且只能被赋值一次,从而保证线程安全性;所以静态内部类实现单例模式兼备了并发高效和懒加载双重优势
枚举实现单例模式
public enum SingletonTest04 { //枚举元素是单例对象 INSTANCE; //添加自己的需要操作 public String SingletonOperator(){ return "连接到数据库实例"; } }
测试代码
public class Client { public static void main(String[] args) { SingletonTest04 s1 = SingletonTest04.INSTANCE; SingletonTest04 s2 = SingletonTest04.INSTANCE; System.out.println(s1==s2); System.out.println(s1.SingletonOperator()); } }
说明:
优点:实现简单;枚举本身就是单例模式,由JVM从根本上保证了线程安全,同时避免了反射和序列化的漏洞!
缺点:没有懒加载