核心作用:
保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
常见应用场景:
Windows的Task Manager(任务管理器)就是很典型的单例模式
windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。
网站的计数器,一般也是采用单例模式实现,否则难以同步。
应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操 作,否则内容不好追加。
数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
Application 也是单例的典型应用(Servlet编程中会涉及到)
在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理
在servlet编程中,每个Servlet也是单例
在spring MVC框架/struts1框架中,控制器对象也是单例
单例模式的优点:
由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决
单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理
常见的五种单例模式实现方式:
主要:
饿汉式(线程安全,调用效率高。 但是,不能延时加载。)
懒汉式(线程安全,调用效率不高。 但是,可以延时加载。)
其他:
双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)
静态内部类式(线程安全,调用效率高。 但是,可以延时加载)
枚举单例(线程安全,调用效率高,不能延时加载)
饿汉式(线程安全,调用效率高。 但是,不能延时加载。)
package singleton;
/**
*@author Danbro
*@version 创建时间:2019年6月26日下午3:19:48
*@funcition 饿汉式单例模式
**/
public class SingTonDemo01 {
//构造器私有化
private SingTonDemo01() {
}
//提供一个静态属性
//为什么叫饿汉式 因为当这个类初始化时立即加载这个对象,不管后面有没有使用这个对象
//缺点 如果这个加载对象比较耗时 则在后面用的时候发现还没加载完成
private static SingTonDemo01 instance = new SingTonDemo01();
//提供一个公共方法 返回对象
//线程安全 类加载过程是一个天然的线程安全的模式
//方法没有同步 调用效率高
public static SingTonDemo01 getInstance() {
return instance;
}
public static void main(String[] args) {
SingTonDemo01 s1 = new SingTonDemo01().getInstance();
SingTonDemo01 s2 = new SingTonDemo01().getInstance();
System.out.println(s1 == s2);
}
}
懒汉式(线程安全,调用效率不高。 但是,可以延时加载。)
package singleton;
/**
*@author Danbro
*@version 创建时间:2019年6月26日下午3:42:51
*@funcition 懒汉式单例模式
**/
public class SingTonDemo02 {
private static SingTonDemo02 instance;
private SingTonDemo02() {
}
//方法要同步 调用效率低. 真正用的时候才加载对象
private static synchronized SingTonDemo02 getInstance() {
if (instance == null) {
instance = new SingTonDemo02();
}
return instance;
}
public static void main(String[] args) {
SingTonDemo02 s1 = new SingTonDemo02().getInstance();
SingTonDemo02 s2 = new SingTonDemo02().getInstance();
System.out.println(s1 == s2);
}
}
双重检测锁模式(问题: 由于编译器优化原因和JVM底层内部模型原因,偶尔会出问题。不建议使用)
public class SingletonDemo03 {
private static SingletonDemo03 instance = null;
public static SingletonDemo03 getInstance() {
if (instance == null) {
SingletonDemo03 sc;
synchronized (SingletonDemo03.class) {
sc = instance;
if (sc == null) {
synchronized (SingletonDemo03.class) {
if(sc == null) {
sc = new SingletonDemo03();
}
}
instance = sc;
}
}
}
return instance;
}
private SingletonDemo03() {
}
}
静态内部类式(调用效率高 并实现延时加载,线程安全)
package singleton;
/**
*@author Danbro
*@version 创建时间:2019年6月26日下午3:57:27
*@funcition 静态内部类实现单例模式 调用效率高 并实现延时加载,线程安全
**/
public class SingTonDemo04 {
private SingTonDemo04() {
}
private static class SingleTonClassInstance{
private static final SingTonDemo04 instance = new SingTonDemo04();
}
public static SingTonDemo04 getInstance() {
return SingleTonClassInstance.instance;
}
public static void main(String[] args) {
SingTonDemo04 s1 = new SingTonDemo04().getInstance();
SingTonDemo04 s2 = new SingTonDemo04().getInstance();
System.out.println(s1 == s2);
}
}
要点:
外部类没有static属性,则不会像饿汉式那样立即加载对象。 只有真正调用getInstance(),才会加载静态内部类。加载类时是线程 安全的。 instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性. 兼备了并发高效调用和延迟加载的优势!
使用枚举实现单例模式
package singleton;
/**
*@author Danbro
*@version 创建时间:2019年6月26日下午4:03:05
*@funcition 枚举式实现单例模式,没有懒加载
**/
public enum SingTonDemo05 {
//这个枚举元素本身就是单例
INTANCE;
//添加自己需要的操作
public static void singleTonOperition() {
}
public static void main(String[] args) {
SingTonDemo05 s1 = SingTonDemo05.INTANCE;
SingTonDemo05 s2 = SingTonDemo05.INTANCE;
System.out.println(s1 == s2);
}
}
优点: 实现简单 枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!
缺点: 无延迟加载
如何选用?
单例对象 占用 资源 少,不需要 延时加载:
枚举式 好于 饿汉式
单例对象 占用 资源 大,需要 延时加载:
静态内部类式 好于 懒汉式