目录
1 单例模式概念
2 单例模式的演示
3 使用反射和序列化破解懒汉单例模式 以及如何防漏洞
概念
单例模式,就是一个类只有一个实例对象,不管怎么做,都只有这个一个实例对象
单例模式优点:只生成一个实例,减少了性能开销,当一个对象的生产需要比较多的资源时,如读取配置 产生其他依赖对象时,则可以通过应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
单例模式可以在系统设置全局的访问点,优化环共享资源访问时,例如可以设计一个单类负责所有数据表的映射处理
常见的五种单例模式的实现方式 主要:1 饿汉模式(线程安全,调用效率高,但是不能延迟加载) 2懒汉模式(线程安全,调用效率不高,可以延时加载)
其他 1 双重检测锁模式(由于jvm底层模型原因,会出问题,不推荐使用)2 静态内部类式(线程安全,调用效率高,但是,可以延迟加载,顾全了 饿汉模式和懒汉模式的有点,但又没有其缺点)3 枚举单例(线程安全,调用效率高,不能延时加载)
代码示例 一 懒汉模式 (线程安全,调用效率不高,能延迟加载)
package singleton;
public class SingletonDemo1 {
// 将构造方法私有化,防止外部访问,因此不可new出此类的对象
private SingletonDemo1() {
// TODO Auto-generated constructor stub
}
// 定义静态的本类对象
private static SingletonDemo1 sing;
// 写一个公开方法给外部调用 通过该方法获取实例对象 加上synchronized设为线程安全的
public static synchronized SingletonDemo1 getInstance() {
// 如果sing==null表明还没有被new new出一个sing返回 否则直接返回sing
if (sing == null) {
sing = new SingletonDemo1();
return sing;
} else {
return sing;
}
}
}
package singleton;
public class SingletonTest {
public static void main(String[] args) {
SingletonDemo1 d1 = SingletonDemo1.getInstance();
SingletonDemo1 d2 = SingletonDemo1.getInstance();
SingletonDemo1 d3 = SingletonDemo1.getInstance();
System.out.println(d1);
System.out.println(d2);
System.out.println(d3);
}
}

对象内存地址一致
代码示例 二 饿汉模式 (线程安全,调用效率高,但是不能延迟加载)
package singleton;
public class SingletonDemo02 {
// 将构造方法私有化,防止外部访问,因此不可new出此类的对象
private SingletonDemo02() {
// TODO Auto-generated constructor stub
}
// 定义静态的本类对象 直接new出实例
private static final SingletonDemo02 sing = new SingletonDemo02();
// 写一个公开方法给外部调用 通过该方法获取实例对象 加上synchronized设为线程安全的
public static synchronized SingletonDemo02 getInstance() {
return sing;
}
}
package singleton;
public class SingletonTest {
public static void main(String[] args) {
SingletonDemo02 d1 = SingletonDemo02.getInstance();
SingletonDemo02 d2 = SingletonDemo02.getInstance();
System.out.println(d1);
System.out.println(d2);
}
}

内存地址一致
示例三 内部静态类方式 (线程安全,调用效率高,但是,可以延迟加载,顾全了 饿汉模式和懒汉模式的有点,但又没有其缺点)

package singleton;
public class SingletonDemo03 {
//定义一个静态内部类
private static class SingStatic {
private static final SingletonDemo03 sing = new SingletonDemo03();
}
//公开的方法,不需要加synchronized 因为他本身就是线程安全的
public static SingletonDemo03 getInstance() {
return SingStatic.sing;
}
}
package singleton;
public class SingletonTest {
public static void main(String[] args) {
SingletonDemo03 d1 = SingletonDemo03.getInstance();
SingletonDemo03 d2 = SingletonDemo03.getInstance();
System.out.println(d1);
System.out.println(d2);
}
}
示例四 枚举式单例模式 (线程安全,调用效率高,不难延时加载)
package singleton;
/**
* 枚举方式可以避免 反射和反序列化的漏洞,调用效率也比较高,很遗憾没有延迟加载
*
* @author re
*
*/
public enum SingletonDemo04 {
// 这个枚举元素本身就是单例对象
instance;
// 可以写自己想要的操作
public void printenum() {
System.out.println("这是枚举示例");
}
}
package singleton;
public class SingletonTest {
public static void main(String[] args) {
SingletonDemo04 d1 = SingletonDemo04.instance;
SingletonDemo04 d2 = SingletonDemo04.instance;
System.out.println(d1 == d2);
}
}

比较为true 是同一个对象
破解单例模式 和防漏洞 (枚举无法破解,因为它是基于JVM底层实现的)
一懒汉模式为例,使用反序列化和反射进行破解
1 使用反射破解
package singleton;
public class SingletonDemo1 {
// 将构造方法私有化,防止外部访问,因此不可new出此类的对象
private SingletonDemo1() {
// TODO Auto-generated constructor stub
}
// 定义静态的本类对象
private static SingletonDemo1 sing;
// 写一个公开方法给外部调用 通过该方法获取实例对象 加上synchronized设为线程安全的
public static synchronized SingletonDemo1 getInstance() {
// 如果sing==null表明还没有被new new出一个sing返回 否则直接返回sing
if (sing == null) {
sing = new SingletonDemo1();
return sing;
} else {
return sing;
}
}
}
package singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class SingletonTest {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// 获取class对象
Class clazz = SingletonDemo1.class;
// 获取构造器
Constructor c1 = clazz.getDeclaredConstructor(null);
c1.setAccessible(true);// 跳过访问检查 如果不设为true 则无法访问私有属性,
// 因此可以访问私有的构造方法,从而创建对象,利用这个漏洞可以破解单例模式
SingletonDemo1 d1 = (SingletonDemo1) c1.newInstance(null);
SingletonDemo1 d2 = (SingletonDemo1) c1.newInstance(null);
System.out.println(d1);
System.out.println(d2);
}
}

输出的 对象地址不一样 将SinglentoDemo1中的私有构造方法改为 可防止漏洞,不过我的这个有些问题,加上之后当sing!=null new对象时,并不能抛出异常。
private SingletonDemo1() {
// TODO Auto-generated constructor stub
if(sing!=null){
//如果有已有sing实例,表示被new过,再创建对象时抛出异常
throw new RuntimeException();
}
}
反序列化防止漏洞方法
在单例类中加入 下面这个方法 方法名称和返回值类型不要改动
// 在反序列化时,直接调用这个方法,返回指定的对象,无需再新建一个对象
private Object readResolve() {
return sing;
}
枚举式单例模式和静态内部类单例模式如何选择
