Java 五种单例模式

单例模式
  • 确保一个类只有一个实例,并提供该实例的全局访问点。
  • 使用一个私有构造函数、一个私有静态变量以及一个公有静态函数来实现。私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。
  • 懒汉模式:当程序第一次访问单例模式实例时才进行创建。如果没有用到该类,那么就不会实例化,从而节约资源。
  • 饿汉模式:在程序启动或单例模式类被加载的时候,单例模式实例就已经被创建。

在这里插入图片描述

单例模式实现的五种方式:

1. 饿汉模式 (线程安全)

public class HungrySingle {
	//这里方法均为static,其他类可直接使用。
	private static final HungrySingle single = new HungrySingle();
	
	private HungrySingle(){}
	//如果自己不创建构造方法,会自动创建无参public构造方法
	public static HungrySingle getInstance(){
		return single;
	}
}

饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。但没有懒加载(延时加载)效果,内存使用率低。

JDK 中的 Runtime 类就是使用饿汉模式实现的单例。

2. 懒汉模式 (线程安全,效率低)

每次获取对象都需要加锁,释放锁,效率低

public class LazySingle {

	private static LazySingle single;
	
	private LazySingle(){}
	
	public static synchronized LazySingle getInstance(){
			if(single == null){
				single = new LazySingle();
			}
			return single;
	}
}

下面是一种懒汉模式线程不安全的写法,多线程下可能创造出多个实例。

public class LazySingle {

	private static LazySingle single;
	
	private LazySingle(){}
	
	public static LazySingle getInstance(){
		if(single == null){
			single = new LazySingle();
		}
		return single;
	}
}

3. 懒汉模式 (双重校验锁式)

双重校验锁式 DCL (Double Check Lock) ,JDK 1.5 以前使用不安全。

public class LazySingle {

	private volatile static LazySingle single;
	
	private LazySingle(){}
	
	public static LazySingle getInstance(){
		if(single == null){
			synchronized (LazySingle.class) {
			//对类对象加锁,所有对象均不能同时运行。
				if(single == null){
					single = new LazySingle();
				}
			}
		}
		return single;
	}
}

详细请看 -> Java 懒汉模式之Volatile优化

4. 懒汉模式 (静态内部类写法)

这种方式能达到双检锁方式一样的功效,但实现更简单。这种方式利用了 类加载机制 来保证初始化实例时只有一个线程,从而保证线程安全
这种方式当 LazySingle 类被装载了,静态内部类 SingleHolder 不会被初始化。因为 SingleHolder 类没有被主动使用,只有显式通过调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance,实现懒加载。

内部类什么时候加载?

内部类都是延时加载的,也就是说只会在第一次使用时加载。不使用就不加载,所以可以很好的实现单例模式。

类加载时怎么保证线程安全?

双亲委派模型保证类不会被加载多次。

public class LazySingle {

	private static class SingleHolder{
		private static final LazySingle instance = new LazySingle();
	}

	private LazySingle() {}

	public static LazySingle getInstance() {
		return SingleHolder.instance;
	}

}

5. 枚举

public enum Singleton {

    INSTANCE;

    private String objName;


    public String getObjName() {
        return objName;
    }


    public void setObjName(String objName) {
        this.objName = objName;
    }


    public static void main(String[] args) {
        // 单例测试
        Singleton firstSingleton = Singleton.INSTANCE;
        firstSingleton.setObjName("firstName");
        System.out.println(firstSingleton.getObjName());
        Singleton secondSingleton = Singleton.INSTANCE;
        secondSingleton.setObjName("secondName");
        System.out.println(firstSingleton.getObjName());
        System.out.println(secondSingleton.getObjName());

        // 反射获取实例测试
        try {
            Singleton[] enumConstants = Singleton.class.getEnumConstants();
            for (Singleton enumConstant : enumConstants) {
                System.out.println(enumConstant.getObjName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果

firstName
secondName
secondName
secondName

该实现既能保证线程安全,又能防止反序列化重新创建新的对象。

该实现在多次序列化再进行反序列化之后,不会得到多个实例。而其它实现需要使用 transient 修饰所有字段,并且实现序列化和反序列化的方法。

该实现可以防止反射攻击。在其它实现中,通过 setAccessible() 方法可以将私有构造函数的访问级别设置为 public,然后调用构造函数从而实例化对象,如果要防止这种攻击,需要在构造函数中添加防止多次实例化的代码。该实现是由 JVM 保证只会实例化一次,因此不会出现上述的反射攻击。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值