单例模式介绍
- 所谓类的单例设计模式,就是采取一定的方法保证整个软件系统中对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
- 单例模式有八种方式
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式 (线程安全,同步方法)
- 懒汉式(线程不安全,同步代码块)
- 双重检查
- 静态内部类
- 枚举
饿汉式(静态常量)
步骤如下:
- 构造器私有化
- 类的内部创建一个私有静态常量对象
- 向外暴露一个静态的公共方法。
代码如下:
/**
* @author fangyajun
* @description 饿汉式(静态常量)
* @since 2019/11/28
*/
public class SingleTon01 {
private SingleTon01(){}
private static final SingleTon01 INSTANCE = new SingleTon01();
public static SingleTon01 instance() {
return INSTANCE;
}
}
分析说明:
- 优点:这种写法比较简单,就是在类装载的时候完成实例化,避免线程同步的问题。
- 缺点:在类装载的时候完成实例化,没有达到Lzay Loading的效果,如果自始至终都没有用过这个实例,就会造成内存浪费。
- 这种方式基于classloder机制避免了多线程同步的问题,不过instance在类装载的时候就实例化了,在单例模式中大多都是调用instance方法,但是导致内装载的原因有很多种,因此不能确定有其他方法导致类装载,这个时候初始化就是饿汉式的。
- 结论:这种单例模式可用,但可能造成内存浪费
饿汉式(静态代码块)
代码如下:
class SingleTon02 {
private SingleTon02(){}
private static SingleTon02 instance;
static {
instance = new SingleTon02();
}
public static SingleTon02 instance() {
return instance;
}
}
分析说明:
- 这种方式和第一种方式类似,只不过将类的实例化放在了静态代码块中,也是在类的装载的时候完成实例化,优缺点和上面一样。
- 结论:这种单例模式可用,但是可能造成内存浪费
懒汉式(线程不安全)
代码如下:
public class SingleTon03 {
private SingleTon03() {}
private static SingleTon03 singleTon03;
public static SingleTon03 getInstance() {
if (singleTon03 == null) {
singleTon03 = new SingleTon03();
}
return singleTon03;
}
}
分析说明:
- 起到了Lazy Loading的效果,但是只能在单线程下使用。
- 如果在多线程下,一个线程进入了if (singleTon03 == null)判断语句块,还未来得及往下执行,另一个线程也进入了这个判断语句,这时候便会产生多个实例,所以多线程下不可使用这个方式
- 结论:在实际开发过程中,不要使用这种方式
懒汉式(线程安全,同步方法)
代码如下:
public class SingleTon04 {
private SingleTon04() {}
private static SingleTon04 singleTon03;
// 加入了同步锁,同时只能有一个线程进入方法内部
public static synchronized SingleTon04 getInstance() {
if (singleTon03 == null) {
singleTon03 = new SingleTon04();
}
return singleTon03;
}
}
分析说明:
- 解决了多线程先线程不安全的问题
- 此种方法在多线程下,同时只能有一个线程进入方法内部,效率太低。而其实这个方法只执行一次实例化代码就够了,后面想获取得改类的实例。直接return就行了,而该方式不管怎么样同时只能一个线程进入方法内部,效率低
- 结论:在实际开发中,不推荐使用这种方式
懒汉式(同步代码块,线程不安全)
代码如下:
public class SingleTon05 {
private SingleTon05() {}
private static SingleTon05 singleTon03;
public static SingleTon05 getInstance() {
if (singleTon03 == null) {
synchronized (SingleTon05.class) {
singleTon03 = new SingleTon05();
}
}
return singleTon03;
}
}
分析说明:
- 这种方法其实是想对上面那种进行改进,改为同步代码块,但是仔细分析我们发现,这种同步其实不能起到线程同步的作用,假如一个线程进入了if (singleTon03 == null)判断语句块还未来得及往下执行,另一个线程也进入了这个判断语句还未进入同步代码块,这时候变会产生多个实例。
- 结论:在实际开发中,不能使用这种方式
懒汉式(双重检查)
代码如下:
public class SingleTon06 {
private SingleTon06() {}
private static SingleTon06 singleTon06;
public static SingleTon06 getInstance() {
if (singleTon06 == null) {
synchronized (SingleTon06.class) {
if (singleTon06 == null) {
singleTon06 = new SingleTon06();
}
}
}
return singleTon06;
}
}
分析说明:
- 这方式是对上一种同步代码块线程不安全的改进,我们进行了2次if (singleTon06 == null)判断,从而保证了线程安全。
- 这样实例化代码块只用执行一次,后面再次访问时,判断if (singleTon06 == null)就直接return返回已经实例化好的对象,从而达到线程安全和Lazy Loading的目的,还有解决了同步方法效率的问题,
- 线程安全,延迟加载,效率较高
- 结论:在实际开发中,
这种单例设计模式
推荐使用
懒汉式(静态内部类)
代码如下:
public class SingleTon07 {
private SingleTon07() {}
private static class SingletonInstance {
private static final SingleTon07 SINGLE_TON_07 = new SingleTon07();
}
public static SingleTon07 instance() {
return SingletonInstance.SINGLE_TON_07;
}
}
分析说明:
- 这种方式采用了类装载机制来保证初始化实例时只有一个线程。
- 静态内部类方式在SingleTon07类被装载的时候不会被立即实例化,而是在需要实例化的时候,调用instance方法才会装载内部类SingletonInstance ,从未完成SingleTon07的实例化,达到了Lazy Loading的目的。
- 类的静态属性只会在第一次加载类的时候初始化,JVM帮助我们保证了线程的安全性,在类进行初始化的时候,别的线程是无法进入的。
- 优点: 避免了线程不安全,利用了静态内部类特点实现延迟加载,效率高
- 结论: 推荐使用
枚举
代码如下:
enum SingleTon08 {
INSTANCE;
}
测试如下:
class Test01 {
public static void main(String[] args) {
SingleTon08 instance = SingleTon08.INSTANCE;
SingleTon08 instance2 = SingleTon08.INSTANCE;
System.out.println(instance == instance2);
}
}
结果输出为:
true
分析说明:
- 这是借助JDK1.5中添加的的枚举来实现的单例模式,不仅能避免多线程同步问题,而且还能防止反复重新创建对象,
- 这种方式是是提倡使用的。
- 结论:推荐使用
总结:
- 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些频繁创建销毁的对象,使用单例模式可以提高系统性能
- 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不new
- 单例模式的使用场景:相应频繁创建和销毁的对象,创建对象时耗时过多或者耗费资源过多(重量级对象),但有经常用到的对象,工具类对象,频繁访问数据库和文件的对象(如:数据源,session工厂等)
- 针对以上8种单例模式,第1,2,6,7,8在实现开发中都是可以使用的,可以根据具体场景选择相应的单例模式。对于3,4,5类型的单例模式不推荐使用,最好也不要使用。