单例模式(Singleton Pattern):保证一个类仅有一个对象,并提供一个访问它的全局访问点。(Ensure a class only has one instance,and provide a globe point of access to it.)
常见应用场景:
- Windows的Task Manager(任务管理器)就是很典型的单例模式
- windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
- 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。
- 网站的计数器,一般也是采用单例模式实现,否则难以同步。
- 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作否则内容不好追加。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
- 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
- Application 也是单例的典型应用(Servlet编程中会涉及到)
- 在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理
- 在servlet编程中,每个Servlet也是单例
- 在spring MVC框架/struts1框架中,控制器对象也是单例
- 一个产品注册了一个商标,那么它就是单例的
单例模式的优点:
- 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动 时直接产生一个单例对象,然后永久驻留内存的方式来解决
- 单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计 一个单例类,负责所有数据表的映射处理
常见单例模式有以下7种:
1.饿汉式:先创建后使用,线程安全,占用内存。代码如下:
/*** 饿汉式单例模式* @author cui_yonghua https://blog.csdn.net/cui_yonghua/article/details/90512943*/
public class ClassA {//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象private ClassA(){ }//2.在类的内部创建一个类的实例//类初始化时,立即加载这个对象(没有延时加载的优势)。加载类时,天然的是线程安全的!private static final ClassA instance = new ClassA();//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用//方法没有同步,调用效率高!public static ClassA getInstance(){return instance;}//测试public static void main(String[] args) {ClassA a = ClassA.getInstance();ClassA b = ClassA.getInstance();System.out.println(a==b);}
}
控制台输出的结果如下图:
2.懒汉式:用的时候才创建,线程不安全,加锁会影响效率。资源利用率高了,但是,每次调用getInstance()方法都要同步,并发效率较低。代码如下:
/*** 懒汉式单例模式* @author cui_yonghua https://blog.csdn.net/cui_yonghua/article/details/90512943*/
public class ClassB {//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象private ClassB(){ }//2.在类的内部创建一个类的实例private static ClassB instance ;//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用public static synchronized ClassB getInstance(){if(instance == null) {instance = new ClassB();}return instance;}//测试public static void main(String[] args) {ClassB a = ClassB.getInstance();ClassB b = ClassB.getInstance();System.out.println(a==b);}
}
控制台输出的结果如下图:
3.静态内部类方式:也即饿汉式和懒汉式的组合,调用getInstance()方法时才创建,达到了类似懒汉式的效果,同时又是线程安全的。代码如下:
/*** 使用静态内部类方式实现单例模式* @author cui_yonghua https://blog.csdn.net/cui_yonghua/article/details/90512943*/
public class ClassC {//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象private ClassC(){ }//2.在类的内部创建一个类的实例private static class Holder{private static ClassC instance = new ClassC();}//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用public static ClassC getInstance(){return Holder.instance;}//测试public static void main(String[] args) {ClassC a = ClassC.getInstance();ClassC b = ClassC.getInstance();System.out.println(a==b);}
}
控制台输出的结果如下图:
4.枚举方法:线程安全,实现简单,调用效率高,不能延时加载。枚举本身就是单例模式,由JVM从根本上提供保障并且可以天然的防止反射和反序列化漏洞!需要继承的场景它就不适用了。枚举方式是Effective Java作者提倡的方式。代码如下:
/*** 使用枚举方法实现单例模式* @author cui_yonghua https://blog.csdn.net/cui_yonghua/article/details/90512943*/
public enum ClassD {//定义一个枚举的元素,它就代表了Singleton的一个实例。INSTANCE;//对外部提供调用方法:将创建的对象返回,只能通过类来调用public void otherMethod(){//功能处理}//测试public static void main(String[] args) {ClassD a = ClassD.INSTANCE;ClassD b = ClassD.INSTANCE;System.out.println(a==b);}
}
5.双重校验锁式:通常线程安全,加volatile的作用是禁止指令重排。(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)代码如下:
/*** 使用双重校验锁来实现单例模式* @author cui_yonghua https://blog.csdn.net/cui_yonghua/article/details/90512943*/
public class ClassE {//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象private ClassE(){ }//2.在类的内部创建一个类的实例private volatile static ClassE instance; //volatile作用:保证多线程可以正确处理instance//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用public static ClassE getInstance(){if(instance == null){ //检查实例,如果为空,就进入同步代码块synchronized (ClassE.class){if(instance == null){ //再检查一次,仍未空才创建实例instance = new ClassE();}}}return instance;}//测试public static void main(String[] args) {ClassE a = ClassE.getInstance();ClassE b = ClassE.getInstance();System.out.println(a==b);}
}
控制台输出的结果如下图:
6.使用ThreadLocal实现:线程安全,ThreadLocal采用以空间换时间的方式,为每一个线程都提供一份变量,因此可以同时访问而互不影响。代码如下:
/*** 使用ThreadLocal实现单例模式* @author cui_yonghua https://blog.csdn.net/cui_yonghua/article/details/90512943*/
public class ClassF {//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象private ClassF(){ }//2.在类的内部创建一个类的实例private static final ThreadLocal<ClassF> tls = new ThreadLocal<ClassF>(){@Overrideprotected ClassF initialValue(){return new ClassF();}};//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用public static ClassF getInstance(){return tls.get();}//测试public static void main(String[] args) {ClassF a = ClassF.getInstance();ClassF b = ClassF.getInstance();System.out.println(a==b);}
}
控制台输出的结果如下图:
7.使用CAS锁来实现:(CAS锁(Compare and Swap):比较并交换,是一种有名的无锁算法,属于乐观锁)。用CAS锁来实现单例模式是线程安全的,代码如下:
/*** 使用CAS锁来实现单例模式* @author cui_yonghua https://blog.csdn.net/cui_yonghua/article/details/90512943*/
public class ClassG {//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象private ClassG(){ }//2.在类的内部创建一个类的实例private static final AtomicReference<ClassG> instance = new AtomicReference<ClassG>(); //3.对外部提供调用方法:将创建的对象返回,只能通过类来调用public static final ClassG getInstance(){for(;;){ClassG current = instance.get();if(current != null){return current;}current = new ClassG();if(instance.compareAndSet(null,current)){return current;}}}//测试public static void main(String[] args) {ClassG a = ClassG.getInstance();ClassG b = ClassG.getInstance();System.out.println(a==b);}
}
控制台输出的结果如下图:
如果如果想了解更多设计模式,可点击:设计模式概述 以及 23种设计模式的介绍
如果觉得文章写的不错,也可以小小地打赏一下嘛~ 也期待合作,“码”上改变~