Java单例模式心得

说说Java单例设计模式

说说Java单例设计模式

第一次写博客,请各位多指教。
单例模式是Java中的一种设计模式,指在设计一个类时,需要保证在整个程序运行期间针对该类只存在一个实例。就好像我们生活中一些单例,比如:只能有一个太阳,一个月亮。

设计思路

设计一个单例类,无论用哪种方法实现,都无外乎三步:

1.构造方法私有化,这是为了不让外界通过new的形式产生对象;
2. 在单例类里面自己初始化一个实例;
3. 对外暴露一个方法获取这个实例;
只要实现的上面所说的三步,我们就能实现一个模式。

简单入门

class Single {
	//1.构造方法私有化
	private Single(){}
	//2.自己创建一个对象
	private static Single INSTANCE=new Single();
	//3.对外暴露获取方法
	public static Single getInstance(){
		return INSTANCE;
    }
}
//此时,通过代码测试可以发现输出为true;
Single s1=Single.getInstance();
Single s2=Single.getInstance();
System.out.println(s1==s2);

版本升级

上面实现一个简单的单例模式,但其实上面的单例模式只是一个简单的入门,通过观察可以发现上面的单例其实可以发现,单例对象没有实现懒加载,是一个饿汉式的单例,下面给出了2种饿汉式的单例:

class Single{	
	private Single() {};
	private static class LazyHolder{
		private static final Single INSTANCE=new Single();
	}	
	public static final Single getInstance() {
		return LazyHolder.INSTANCE;
	}
}

这种形式的懒汉式的单例,利用了内部类的特点:内部类不会随着外部类的加载而加载。这样在内部类里面实现外部单例类对象的建立,在用户第一次使用LazyHolder这个内部类的时候,就会先去初始化单例对象,这样就实现了懒加载。而且这种方式,不会存在线程问题,因为类在加载的时候是线程安全的,所以实例只会被创建一次。
下面看用双重检查实现:

class Singleton4{
	private Singleton4() {}
	private static volatile Singleton4 INSTANCE=null;
	public static Singleton4 getInstance() {
		if(INSTANCE==null) {
			synchronized(Singleton4.class) {
					if(INSTANCE==null) {
						INSTANCE=new Singleton4();
					}
				}
			}	
		return INSTANCE;
		}				
	}

这种双重检查的方式,实现了单例,也实现了懒加载,但是要小心的是,一定要加上volatile这个关键词,因为在创建的对象的时候,大概是这三步:
1:分配对象的内存空间;
2:初始化对象;
3:把INSTANCE指向分配的内存空间;
那么其实这三步是有可能被重排序的,如果被重排序就会出现下面这种情况:
1:分配内存空间;
2:把INSTANCE指向分配的内存空间;
3:初始化对象;
一旦发生这样的重排序,那么就有可能第一个线程在第二步时停住了,此时第二个线程到达,他进行第一次检查,因为已经分配了内存空间,所以INSTANCE不为空,那么就会直接返回INSTANCE,但此时的INSTANCE是没有初始化的,那就会出错。但是一旦加上了volatile关键字,就会防止出现重排序。

问题没完

但其实上面说的方法都是有漏洞的,都会被反射和序列化破坏,破坏方法如下:

    Singleton4 instance1 = Singleton4.getInstance();
	Singleton4 instance2 = Singleton4.getInstance();
	//这个依然会输出true,因为还没被破坏
	System.out.println(instance1==instance2);
	//通过反射,得到Singleton4的构造函数,
	Class<?> forName = Class.forName("Singleton4");
	Constructor<?> constructor = forName.getDeclaredConstructor();
	//这里会输出false,isAccessible的意思是可访问的,这是因为我们把构造方法私有化了,所以会输出false;
	System.out.println(constructor.isAccessible());
	//这里的操作是把构造方法设置会可访问的
	constructor.setAccessible(true);
	//进过上面的设置,这里就会输出true
	System.out.println(constructor.isAccessible());
	//这里就可以通过newInstance()来产生一个新的实例,就会输出false
	System.out.println(constructor.newInstance()==instance2);
	

下面是用序列化破坏,代码如下:

	FileOutputStream fileOut =new FileOutputStream("A://Single.ser");
	 ObjectOutputStream out = new ObjectOutputStream(fileOut);
	 out.writeObject(Singleton5.getInstance());
	 FileInputStream fileIn = new FileInputStream("A://Single.ser");
     ObjectInputStream in = new ObjectInputStream(fileIn);
     Singleton5 INSTANCE=(Singleton5) in.readObject();
     System.out.println(INSTANCE==Singleton5.getInstance());

这片代码的意思就是,把一个单例序列化到本地,然后再反序列化读到内存中来,结果会输出false,可见这个单例被序列化破坏了,原因就是反序列化也是通过反射来建立新的对象,所以被破坏了。

那么,解决这个问题方法就是用枚举来实现单例,用枚举实现单例很简单代码如下:

public enum SingletonClass {
    INSTANCE;
	public static SingletonClass getInstance() {
		return INSTANCE;
	}
}

没错就是这么简单就实现的单例,因为枚举在Java中天生就是如此,不用过多的其他操作,如果此时再用上面反射的方法去试图破坏话,会直接报错,因为Java在设计枚举的时候就屏蔽了这种操作。而用序列化去破坏呢,也会输出true的,所以发现序列化不会破坏枚举的单例。

以上就是本次的全部内容,真心希望错误的地方各位能多多指教。感谢,阿门。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值