单例模式的特征:
1.单例类只能有一个实例
2.单例类必须自己创建自己的实例
3.单例类必须给其他类提供这一实例
意图:保证一个类只有一个实例,并提供一个访问它的全局访问点
主要解决:一个全局使用的类频繁地创建与销毁
何时使用:需要控制实例数目,节省系统资源
如何解决:判断系统是否已经有这个实例,如果有则返回;如果没有则创建再返回
关键代码:构造函数是私有的
通过网上资料查询,得到单例模式的6种实现方式:
第一种
package com.yuxx.singleton;
/**
* 懒汉式:懒加载、线程不安全
* 由于没有加锁,严格意义上不算单例模式
*/
public class Singleton1 {
private static Singleton1 instance;
private Singleton1(){}
public static Singleton1 getInstance(){
if(instance == null){
instance = new Singleton1();
}
return instance;
}
}
第二种
package com.yuxx.singleton;
/**
* 懒汉式,懒加载,线程安全
* 优点:第一次调用才初始化,避免内存浪费
* 缺点:必须加锁synchronized才能保证单例,但加锁会影响效率
* 加锁后,相当于同一个时间,只能有一个线程来访问,就降低了并发性
*/
public class Singleton2 {
private static Singleton2 instance;
private Singleton2(){}
public static synchronized Singleton2 getInstance(){
if(instance == null){
instance = new Singleton2();
}
return instance;
}
}
第三种
package com.yuxx.singleton;
/**
* 饿汉式,非懒加载,线程安全
* 优点:没有加锁,执行效率提高
* 缺点:类加载时就初始化,浪费内存,容易产生垃圾对象
*/
public class Singleton3 {
private static Singleton3 instance = new Singleton3();
private Singleton3(){}
public static Singleton3 getInstance(){
return instance;
}
}
第四种
package com.yuxx.singleton;
/**
* 懒加载:线程安全
* 这种方式采用双锁机制,安全且在多线程情况下保持高性能。
*/
public class Singleton4 {
private volatile static Singleton4 instance;
private Singleton4(){}
public static Singleton4 getInstance(){
if(instance == null){
synchronized (Singleton4.class){
if(instance == null){
instance = new Singleton4();
}
}
}
return instance;
}
}
第五种
package com.yuxx.singleton;
/**
* 登记式/静态内部类:懒加载、线程安全
* 在这个方式中,Singleton5类被加载时,instance不会被实例化。因为instance是在SingletonHolder中被实例化。
* 只有在SingletonHolder也被显示装载使用时,才会得到Singleton5的实例。
* 如果实例化Singleton很消耗资源,所以想让它延迟加载,另一方面,又不希望Singleton5类被加载时就实例化,
* 因为不能确保Singleton类还可能在其他地方被主动使用而被加载,那么这个时候,这种方式相比第三种更合理。
*/
public class Singleton5 {
private static class SingletonHolder{
private static final Singleton5 INSTANCE = new Singleton5();
}
private Singleton5(){}
private static final Singleton5 getInstance(){
return SingletonHolder.INSTANCE;
}
}
第六种
package com.yuxx.singleton;
/**
* 枚举:非懒加载、线程安全
* 这个实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。
* 它更简洁,自动支持序列化机制,绝对防止多次实例化。
*/
public enum Singleton6 {
INSTANCE;
public void whateverMethod(){
System.out.println("do everything...");
}
}
关于第六种,我在学习的时候也有疑惑,不能理解枚举类和单例模式甚至实例之间有什么关系。后来通过网上各位前辈整理的资料得知,枚举类在编译后,实质上是一个继承了Enum类的class。因为我暂时没有找到可以查看到这种效果代码的方法,所以引用了前辈们的代码:
原枚举类:
public enum Type {
SELECT, UPDATE, INSERT, DELETE
};
反编译后:
public static final class Type extends Enum{
public static final Type SELECT;
public static final Type UPDATE;
public static final Type INSERT;
public static final Type DELETE;
private static final Type $VALUES[];
public static final Type[] values(){
return (Type[])$VALUES.clone();
}
public static Type valueOf(String name){
return (Type)Enum.valueOf(com/***/***/***/***$Type, name);
}
static {
SELECT = new Type("SELECT", 0);
UPDATE = new Type("UPDATE", 1);
INSERT = new Type("INSERT", 2);
DELETE = new Type("DELETE", 3);
$VALUES = (new Type[] {
SELECT, UPDATE, INSERT, DELETE
});
}
private Type(String s, int i){
super(s, i);
}
}
通过上面的代码,我们可以看出,其实原枚举类Type里的SELECT、UPDATE、INSERT、DELETE这四个元素,本质上就是普通类Type类的四个静态成员变量,类型是Type,并在静态块中被进行了实例化。而且Type类的构造方法被私有化了,这样就避免了被外来类调用并实例化的问题。(关于枚举类的原理,以后再学习进行总结)
而且,在枚举类中,也可以像普通类中去定义普通的成员变量和成员方法,类似这样:
enum Color{
RED,GREEN,BLUE;
private int code;
Color(int code){
this.code=code;
}
public int getCode(){
return code;
}
}
这样,我们普通类可实现的功能,枚举类也可以去实现了。
------------------------以下补充-----------------------
感谢若水提醒,此处补充下Spring中单例的实现:
Spring单例的定义方式可取决于Spring bean的装载方式:spring配置文件 or SpringBean配置类
1.spring配置文件
bean标签有个属性:scope,属性值则表明了这个bean是单实例还是多实例,单实例Singleton,多实例:prototype。没有这个属性值则默认是单实例
2.Bean配置类(Spring注解驱动了解到的)。
此处用的是Spring的@Scope注解,如果没有标明实例方式,也默认是单实例
@Scope("singleton")
@Bean("person")
public Person person(){
System.out.println("向容器中添加Person...");
return new Person("张三",25);
}
@若水的提问,Spring单例使用的上面哪种方式。
单例模式的常用场景,总体来说包括两种:
1.资源共享:例如系统中的应用配置文件的实例,还有一些公用的组件实例
2.通信控制:例如数据库连接池,为避免在使用中频繁打开连接和断开连接会降低整体功能的效率,而使用单例,一次打开,多次使用