单例模式

概述

单例模式的核心是保证一个类只有一个实例,并提供个全局访问点。
单例模式的优点:由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。

饿汉式

package com.fzxey.ton;
/**
 * 单例模式:饿汉模式
 * 
 * 优点:在类加载时初始化,不会涉及线程问题。虚拟机保证只会装载一次。
 * 缺点:如果只是加载本类,而不调用getInstance()方法,
 * 甚至永远不调用,则会造成资源浪费。
 * @author fzxey
 *
 */
public class SingletonInstance1 {
	//声明此类型的常量,并且当该类被加载的时候就完成实例化保存在内存中。
	private static SingletonInstance1 instance = new SingletonInstance1();
	//私有化构造器
	private SingletonInstance1() {}
	//对外提供实例化对象
	public static SingletonInstance1 getIntance() {
		return instance;
	}
}

懒汉式

package com.fzxey.ton;
/**
 * 单例模式:懒汉模式
 * 优点:此方式在类加载后不会实例化对象,只有当我们调用getInstance()方法时才会初始化。
 * 缺点:但是该方法为解决线程问题,在方法上加了synchronized关键字,
 * 每次调用getInstance()方法时都会同步,所以对性能的影响很大
 * @author fzxey
 *
 */
public class SingletonInstance2 {
	//声明此类型的变量,但没有实例
	private static SingletonInstance2 instance = null;
	//私有化构造器
	private SingletonInstance2() {}
	//对外提供实例化方法
	public static synchronized SingletonInstance2 getInstance() {
		if(instance==null) {
			return new SingletonInstance2();
		}
		return instance;
	}
}

双重检测

package com.fzxey.ton;
/**
 * 单例模式:双重检测
 * 优点:将同步内容放到了if中,提高运行效率不必每次获取对象是都进行同步。
 * 缺点:由于编译器优化原因和JVM底层模型原因,有时候出问题,不建议使用。
 * 
 * @author fzxey
 *
 */
public class SingletonInstance3 {
	//声明此类型的变量,但没有实例
	private static SingletonInstance3 instance = null;
	//私有化构造器
	private SingletonInstance3() {}
	//对外提供实例化方法
	public SingletonInstance3 getInstance() {
		if (instance == null) {
			synchronized (SingletonInstance3.class) {
				if (instance == null) {
					instance = new SingletonInstance3();
				}
			}
		}

		return instance;
	}

}

静态内部类

package com.fzxey.ton;
/**
 * 单例模式:静态内部类
 * 一般使用此方式
 * 不会立即加载对象,只有调用getInstance()方法才会加载静态内部类。
 * 加载类时是线程 安全的instance是static final类型。
 * 兼备了并发高效调用和延迟加载的优势。
 * @author fzxey
 *
 */

public class SingletonInstance4 {
	//静态内部类
	public static class SingletonInstance {
		//声明外部类型的静态常量
		public static final SingletonInstance4 instance = new SingletonInstance4();
	}
	//私有化构造器
	private SingletonInstance4() {}
	//对外提供唯一的实例化方法
	public SingletonInstance4 getInstance() {
		return SingletonInstance.instance;
	}
}

枚举

package com.fzxey.ton;
/**
 * 单例模式:枚举单例
 * 优点:实现简单,枚举本身就是单例。
 * 由JVM从根本上提供保障,避免通过反射和反序列化来实例的漏洞。
 * 缺点:无延迟加载
 * @author fzxey
 *
 */
public enum SingletonInstance5 {
	//定义一个枚举元素,该元素代表SingletonInstance5的一个实例
	INSTANCE;
	public void singletonOperation() {
		//功能处理
	}
}

单例模式漏洞

可也通过反射或反序列化来实例化对象(除枚举以外)

反射破解(枚举除外)

package com.fzxey.ton;

import java.lang.reflect.Constructor;

public class Test {
	public static void main(String[] args) throws NoSuchMethodException, Exception{
		SingletonInstance1 s1 = SingletonInstance1.getIntance();
		//通过反射获取实例化对象
		Class c1 = SingletonInstance1.class;
		Constructor con = c1.getDeclaredConstructor();
		con.setAccessible(true);
		SingletonInstance1 s2 = (SingletonInstance1) con.newInstance(null);
		System.out.println(s1);
		System.out.println(s2);
		
	}
}

输出结果

com.fzxey.ton.SingletonInstance1@15db9742
com.fzxey.ton.SingletonInstance1@6d06d69c

其结果产生了两个对象,违背了单例模式的初衷。解决方式是在在无参构造方法中手动抛出异常。

private SingletonInstance1() {
		if(instance!=null) {
			throw new RuntimeException("单例模式只能创建一个对象");
		}
	}

反序列化破解(枚举除外)

package com.fzxey.ton;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Test2 {
	public static void main(String[] args) throws IOException, ClassNotFoundException {
		SingletonInstance2 s1 = SingletonInstance2.getInstance();
		//将实例序列化到文件中
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:/dom/test.txt"));
		oos.writeObject(s1);
		oos.flush();
		oos.close();
		//将实例反序列化出来
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:/dom/test.txt"));
		SingletonInstance2 s2 = (SingletonInstance2) ois.readObject();
		ois.close();
		System.out.println(s1);
		System.out.println(s2);
	}
}

结果:

com.fzxey.ton.SingletonInstance2@5c647e05
com.fzxey.ton.SingletonInstance2@e9e54c2

单例类必须实现序列化才可以用反序列化破解。破解后也是两个不同的对象。解决方法只需要在单例类中重写readResolve方法并在该方法中返回单例对象即可。

package com.fzxey.ton;

import java.io.Serializable;

/**
 * 单例模式:懒汉模式
 * 优点:此方式在类加载后不会实例化对象,只有当我们调用getInstance()方法时才会初始化。
 * 缺点:但是该方法为解决线程问题,在方法上加了synchronized关键字,
 * 每次调用getInstance()方法时都会同步,所以对性能的影响很大
 * @author fzxey
 *
 */
public class SingletonInstance2 implements Serializable{
	//声明此类型的变量,但没有实例
	private static SingletonInstance2 instance = null;
	//私有化构造器
	private SingletonInstance2() {}
	//对外提供实例化方法
	public static synchronized SingletonInstance2 getInstance() {
		if(instance==null) {
			return new SingletonInstance2();
		}
		return instance;
	}
	//重写该方法防止反序列化获取对象
	private Object readResolve() {
		return instance;
	}
}

@Fzxey

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值