您的位置:首页 > 其它

单例模式

2016-07-21 10:49 106 查看
参考原文博客:点击进入

在此谢过原博主。

单例模式有5种形式;分别是
饿汉模式
懒汉模式
双重检测锁模式
静态内部类模式
枚举模式
。其中双重检测锁本人不清楚这么做的原因,并且原博主建议在JDK修复同步块嵌套漏洞之前不使用。
静态内部类模式
咋一看还不如写回
饿汉模式
,但是
静态内部类模式
实现了延迟加载,提供了系统性能。

以下是代码:

(1)饿汉模式

package cn.test.Singleton.one;

/**
* 饿汉式
* 未实现延时加载,线程安全?为什么说是线程安全,
* 因为在类加载的时候就已经实例化了instance,所以当多个线程并发
* 访问getInstance()的时候都是得到同一个实例instance,不会因为
* 并发的问题出现实例化两个对象
* @author cjc
*
*/
public class SingleTon_EH {
private static SingleTon_EH instance = new SingleTon_EH();

private SingleTon_EH(){}

public static SingleTon_EH getInstance(){
return instance;
}

}


(2)懒汉模式

package cn.test.Singleton.two;

/**
* 懒汉式
* 实现了延迟加载
* 线程不安全。为什么线程不安全?
* 因为在多个线程并发访问getInstance()的时候,可能因为
* 并发的问题同时执行if(null == instance){instance = new SingleTon_LH();}
* 这段代码,导致创建了不止一个实例。所以可以在getInstance()加synchronized锁,但是牺牲
* 了高并发性能
*
* 总的来说:
* 实现延迟加载
* 线程安全但是牺牲了高并发性能
*
* @author cjc
*
*/
public class SingleTon_LH {
private static SingleTon_LH instance = null;

private SingleTon_LH(){}

public static synchronized SingleTon_LH getInstance(){
if(null == instance){
instance = new SingleTon_LH();
}
return instance;
}
}


(3)双重检测锁模式

package cn.test.Singleton.three;

/**
* 双重检测锁式单例模式
* 实现延迟加载
* 线程安全
*
* 在jdk修复同步块嵌套之前不推荐,这里只作了解
* @author cjc
*
*/
public class SingleTon_DoubleLock {
private static SingleTon_DoubleLock instance = null;
private SingleTon_DoubleLock(){}

public static SingleTon_DoubleLock getInstance(){
if(null == instance){
SingleTon_DoubleLock st;
synchronized (SingleTon_DoubleLock.class) {
st = instance;
if(null == st){
synchronized (SingleTon_DoubleLock.class) {
if(null == st){

ed52
st = new SingleTon_DoubleLock();
}
instance = st;
}
}
}
}

return instance;
}
}


(4)静态内部类模式

package cn.test.Singleton.four;

/**
* 静态内部类式单例模式
* 实现延迟加载。
* 线程安全
* @author cjc
*
*/
public class SingleTon_staticInner {
private SingleTon_staticInner(){}

private static class Inner{
public static final SingleTon_staticInner instance = new SingleTon_staticInner();
}

public static SingleTon_staticInner getInstance(){
return Inner.instance;
}
}


(5)枚举类模式

package cn.test.Singleton.five;

/**
* 枚举式单例。
* 未延迟加载
* 线程安全
* 原生防止发射与序列化击穿
* @author cjc
*
*/
public enum SingleTon_Enum {
INSTANCE;
}


测试:

package cn.test.Singleton.instance;

import org.junit.Test;

import cn.test.Singleton.five.SingleTon_Enum;
import cn.test.Singleton.four.SingleTon_staticInner;
import cn.test.Singleton.one.SingleTon_EH;
import cn.test.Singleton.three.SingleTon_DoubleLock;
import cn.test.Singleton.two.SingleTon_LH;

public class Test1 {
@Test
public void test01(){
SingleTon_EH s11 = SingleTon_EH.getInstance();
SingleTon_EH s12 = SingleTon_EH.getInstance();
System.out.println(s11 == s12);

SingleTon_LH s21 = SingleTon_LH.getInstance();
SingleTon_LH s22 = SingleTon_LH.getInstance();
System.out.println(s21 == s22);

SingleTon_DoubleLock s31 = SingleTon_DoubleLock.getInstance();
SingleTon_DoubleLock s32 = SingleTon_DoubleLock.getInstance();
System.out.println(s31 == s32);

SingleTon_staticInner s41 = SingleTon_staticInner.getInstance();
SingleTon_staticInner s42 = SingleTon_staticInner.getInstance();
System.out.println(s41 == s42);

SingleTon_Enum s51 = SingleTon_Enum.INSTANCE;
SingleTon_Enum s52 = SingleTon_Enum.INSTANCE;
System.out.println(s51.getClass()+","+s52.getClass());
System.out.println(s51 == s52);

}
}


输出:

true
true
true
true
class cn.test.Singleton.five.SingleTon_Enum,class cn.test.Singleton.five.SingleTon_Enum
true


总结:以上每一个都是线程安全的,其中懒汉模式是通过synchronize保证了线程安全但牺牲了高并发性能。

以上的几种模式都有漏洞,可以用序列化的方式或者是用反射机制去破解,得到两个不同的实例,下面我们看看如何避免这种情况的发生:

(1)序列化

例子:饿汉模式:

代码是:

public class SingleTon_EH implements Serializable{

private static final long serialVersionUID = 1L;

private static SingleTon_EH instance = new SingleTon_EH();

private SingleTon_EH(){}

public static SingleTon_EH getInstance(){
return instance;
}
}


序列化代码:

/**
* 测试序列化,饿汉模式
*/
@Test
public void test03(){
SingleTon_EH instance = SingleTon_EH.getInstance();
ObjectOutputStream out = null;
ObjectInputStream in = null;
try{
out = new ObjectOutputStream(
new FileOutputStream("temp.txt"));
out.writeObject(instance);

//反序列化
in = new ObjectInputStream(
new FileInputStream("temp.txt"));
SingleTon_EH insIn = (SingleTon_EH)in.readObject();
System.out.println(instance);
System.out.println(insIn);
}catch (Exception e) {

}finally{

}
}


那么得到的结果是:

cn.test.Singleton.one.SingleTon_EH@b23b25c
cn.test.Singleton.one.SingleTon_EH@575fadcf


在这里,必须提醒一下,当实例化一个对象,再把这个对象序列化到磁盘,以后每次反序列化得到的对象都是不同的,也就是把in流close关了,再打开,再反序列化,得到的对象和之前反序列化的对象是不同的。

那如何去避免了。我们用序列化知识的一个方法readResolve()方法,readResolve()方法有什么作用呢?readResolve()是序列化机制的一个特殊的方法,它可以实现保护性复制整个对象。这个方法会紧接着readObject()或defaultReadObject()被调用,替换读取到的序列化对象,并且将它返回,修改饿汉模式代码如下:

public class SingleTon_EH implements Serializable{

private static final long serialVersionUID = 1L;

private static SingleTon_EH instance = new SingleTon_EH();

private SingleTon_EH(){}

public static SingleTon_EH getInstance(){
return instance;
}

/**
* 如何避免反序列化得到不同的实例呢?
* 反序列化时,如果定义了readResolve(),则直接次方法指定的对象
*/
public Object readResolve(){
return instance;
}
}


再次执行test03()方法得到

cn.test.Singleton.one.SingleTon_EH@b23b25c
cn.test.Singleton.one.SingleTon_EH@b23b25c


同样的道理,将其他模式都一样的加上以上代码就可以防止序列化了。

(2)反射

反射机制很强大,可以得到private的构造器,并且将private的构造器设为可访问的,那么就可以通过反射得到的构造器new出一个实例出来了,这样又破坏了单例模式只有唯一的实例的情况。

例子:懒汉模式

public class SingleTon_LH {
private static SingleTon_LH instance = null;
public static synchronized SingleTon_LH getInstance(){
if(null == instance){
instance = new SingleTon_LH();
}
return instance;
}
/**
* 如何避免反序列化得到不同的实例呢?
* 反序列化时,如果定义了readResolve(),则直接次方法指定的对象
*/
public Object readResolve(){
return instance;
}
}


反射机制代码:

/**
* 反射破解懒汉模式的单例模式,其他模式同理,但是枚举模式原生避免这种风险
* @throws Exception
*/
@Test
public void test02() throws Exception{
//反射加载类
Class<SingleTon_LH> clazz = (Class<SingleTon_LH>) Class.forName("cn.test.Singleton.two.SingleTon_LH");
//反射获得构造器
Constructor<SingleTon_LH> contructor = clazz.getDeclaredConstructor(null);  //无参构造器

contructor.setAccessible(true);

SingleTon_LH s1 = contructor.newInstance(); //无参构造器,参数为空
SingleTon_LH s2 = contructor.newInstance();
SingleTon_LH s3 = SingleTon_LH.getInstance();
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
}


得到的结果:

cn.test.Singleton.two.SingleTon_LH@5430d082
cn.test.Singleton.two.SingleTon_LH@50c931fc
cn.test.Singleton.two.SingleTon_LH@48f0c0d3


但是当我们加上:

contructor.setAccessible(true);
SingleTon_LH ss = SingleTon_LH.getInstance();
System.out.println(ss);


这段代码后,程序执行就会保存,报出现运行时错误,这是因为执行了以上代码后instance已经不再是null了。

其他模式的代码一样,只有懒汉模式有一个漏洞,就是当没有执行过getInstance()时,才不能避免反射机制的破坏,修改代码如下:

public class SingleTon_LH {
private static SingleTon_LH instance = null;

private SingleTon_LH(){
if(instance != null){
throw new RuntimeException();
}
}

public static synchronized SingleTon_LH getInstance(){
if(null == instance){
instance = new SingleTon_LH();
}
return instance;
}
/**
* 如何避免反序列化得到不同的实例呢?
* 反序列化时,如果定义了readResolve(),则直接次方法指定的对象
*/
public Object readResolve(){
return instance;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: