Singleton mode
首先,单例模式属于创建型模式,它的实现方式也有多种
这里,先思考一下,
一些单例的实现方式是真的实现了真正意义的单例吗?
答案是:不是的。不管是是否加锁了,仍然可通过其他方式破解单例。请看分析
下面呢,我会从它的以下方面并结合代码进行分析。
- 常见写法
- 作用
- 不同实现方式
- 每个实现方式的优缺点
常见写法
写这个呢,是为了能够使初学者有一个大概的认识,可能在分析别人的代码时见过熟悉,但当时可能并不知道它是单例模式的实现。
public class Singleton{
private static Singleton mInstance;
private Singleton(){
}
public static Singleton getInstance(){
if (mInstance == null){
mInstance = new Singleton();
}
return mInstance;
}
public void getUserInfo(){
}
}
//外部类要访问getUserInfo()方法
Singleton.getInstance.getUserInfo();
这个呢,可能是大家经常看到的单例模式实现。
作用
它的使用场景还是比较普遍的,比如大家在分析源码时会看到单例模式,或者自己去搭建项目结构中(或者在写一个功能类时)会用上单例模式,那么用它的目的是什么呢?
可以简单分析一下上面这个例子,首先,通常我们在使用一个类的时候呢是通过new这个类,得到这个类的实例,调用类中的方法,
这里呢,构造函数是private声明的,外部不能直接new,外部要想使用只能通过getInstance()静态方法,方法中if(mInstance == null) 会直接new一个并赋值给mInstance,不为空时直接return这个实例,可以看出并不是每次都直接去new一个实例,这样就减少了它的实例的创建。
核心用作:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点
什么意思呢?就是,这个类的实例一旦创建,它就属于一个静态全局变量,它的生命周期属跟随整个应用程序,这个类只有一个实例,其他类需要访问这个类的方法,不需要再重新new一个实例对象。
不同实现方式
1.饿汉式(单例对象立即加载)
public class HungrySington {
private static HungrySington mInstance = new HungrySington();
private HungrySington(){
//防止通过反射得到这个构造器,下面会讲这里判断的原因
if(mInstance != null)
throw new RuntimeException();
}
public static HungrySington getInstance(){
return mInstance;
}
public void getUserInfo(){
}
}
特点:当类加载时就会直接创建这个类的实例,也就你不管你用不用,会先创建出来(会造成空间的浪费),那么之后调用的时候就不需要再判断了,节省了运行时间,属于空间换取时间。
2.懒汉式
普通懒汉式方式:
public class LazySinglton {
private static LazySinglton mInstance;
private LazySinglton (){
//防止通过反射得到这个构造器
if(mInstance != null)
throw new RuntimeException();
}
public static LazySinglton getInstance() {
if (mInstance == null) {
mInstance = new LazySinglton();
}
return mInstance;
}
public void getUserInfo(){
}
}
但是,懒汉式存在这线程安全问题,线程不安全,需要加上同步锁
public class LazySinglton {
private static LazySinglton mInstance;
private LazySinglton (){
//防止通过反射得到这个构造器
if(mInstance != null)
throw new RuntimeException();
}
public static synchronized LazySinglton getInstance() {
if (mInstance == null) {
mInstance = new LazySinglton();
}
return mInstance;
}
public void getUserInfo(){
}
}
特点:懒汉式呢,会在真正用到时创建实例,节省不必要的空间浪费。
考虑多线程访问的线程安全问题,加上了同步锁,同样的,加上同步锁影响了程序执行效率。
3.双重锁
public class DoubleCheckSinglton {
private volatile static DoubleCheckSinglton mInstance; //volatile声明
private DoubleCheckSinglton (){
//这里也要加判断,防止通过反射访问到构造器,进而创建实例
}
public static DoubleCheckSinglton getSingleton() {
if (mInstance == null) {
synchronized (DoubleCheckSinglton.class) {
if (mInstance == null) {
mInstance = new DoubleCheckSinglton();
}
}
}
return mInstance;
}
}
4.静态内部类
public class StaticSinglton {
private StaticSinglton(){
//这里也要加判断,防止通过反射访问到构造器,进而创建实例
}
//静态内部类
private static class SingletonHolder{
//INSTANCE是static final 类型,保证了内存中只有一个这样的实例存在,而且只被赋值一次,保证了线程安全
private static final StaticSinglton INSTANCE = new StaticSinglton();
}
//只有真正调用了getInstance,才会加载这个静态内部类
public static StaticSinglton getInstance(){
return SingletonHolder.INSTANCE;
}
}
特点:静态内部类在初始化过程中是不会被加载的,只有当用户调用getInstance方法时才会加载内部类,并且实例化StaticSinglton实例INSTANCE
它符合的特点是:
1.只有在需要的时候创建实例,
2.线程安全,原因是static final StaticSinglton INSTANCE,它是静态内部类只会被实例化一次,在多线程访问的情况下,线程拿到的都是同一个实例。
5.枚举
public enum EnumSinglton {
//枚举方式
//枚举本身就是单例对象,由JVM从根本上提供保障,避免通过反射和反序列化的漏洞创建新的对象
// !!!!!!!!!!!! 即使构造器私有了 ,也能通过反射去调用
//缺点:无延迟加载
//定义一个枚举元素,他就代表了EnumSinglton的一个实例
INSTANCE;
public void getUserInfo(){
}
}
//外部调用
EnumSinglton.INSTANCE
特点:上面注释也标明了。
注意:
在私有构造函数中加了判断,
if(mInstance != null)
throw new RuntimeException();
原因是通过反射,仍然可以得到构造器
在讲“反射”的时候会讲是如何通过反射去调用的。
单例的作用就是保证应用程序只有一个实例存在,而通过反射是可以获取到这个构造器,
那么可想而知,在外部创建多少的对象是不受控制了。
所以不这样做其实也就没有做到真正意义上的单例。
简单测试 如何通过反射破解单例 :
public class DecodeSinglton {
public static void main(String[] args) throws Exception {
HungrySington c1 = HungrySington.getInstance();
HungrySington c2 = HungrySington.getInstance();
System.out.println(c1);
System.out.println(c2);
//通过反射和反序列化破解单例模式
//通过反射找到这个类
Class<HungrySington> cls = (Class<HungrySington>) Class.forName(
"com.interview.javacontent.designpattern.singlton.HungrySington");
//得到这个类的构造器
Constructor<HungrySington> c = cls.getDeclaredConstructor(null);
//因为我们设置HungrySington的构造器是私有的,那么我们来跳过权限检测
c.setAccessible(true);
HungrySington cls3 = c.newInstance();
HungrySington cls4 = c.newInstance();
System.out.println(cls3);
System.out.println(cls4);
}
}
以上内容,如有问题,可留言修正哦,一起探讨技术,一起成长。