核心作用:
- 保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
常见应用场景:
- windows的任务管理者(Task Manager)就是很典型的单例模式;
- 在spring中,每个Bean默认就是单例的,这样做的优点是spring容器可以管理;
- 在servlet编程中, 每个servlet也是单例;
- 项目中,读取配置文件的类,一般也只有一个对象,没有必要每次使用配置文件数据,每次new一个对象去读取。
单例模式优点:
- 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其它依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式类解决。
- 单例模式可以在系统设置全局的访问点,优化共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
常见的五种单例模式实现方式:
主要:
- 饿汉式(线程安全,调用效率高。但是,不能延时加载)
- 懒汉式(线程安全,调用效率低。但是,可以延时加载)
其它:
- 双重检查锁(线程安全,可以延时加载。建议使用)
- 静态内部类(线程安全,调用效率高。并且,可以延时加载)
- 枚举单例(天生线程安全,调用效率高。但是,不能延时加载)
饿汉式:
java
复制代码
/**
* 饿汉式 --->线程安全,方法没有同步,效率高!但是,没有延时加载优势
* 1,构造器私有化,避免外部直接创建对象
* 2,声明一个私有的静态属性 并 创建该对象
* 3,创建一个对外的公共的静态方法,访问该变量,
* @author admin
*
*/
public class SingLeton {
/**
* 2,声明一个私有的静态属性 并 创建该对象
* 类初始化时,立即加载这个对象(没有延时加载优势),加载类时,天然的是线程安全的!(所有。。)
*/
private static SingLeton instance =new SingLeton();
/**
* 1,构造器私有化,避免外部直接创建对象
*/
private SingLeton() {
}
/**3,创建一个对外的公共的静态方法,访问该变量。
* 方法没有同步,效率高!
* @return
*/
public static SingLeton getInstance(){ //
return instance;
}
}
懒汉式:
java
复制代码
/**
* 懒汉式--->线程安全,调用效率不高!但是,可以延时加载
* 1,构造器私有化,避免外部直接创建对象
* 2,声明一个私有的静态属性
* 3,创建一个对外的公共的静态方法,访问该变量,如果变量没有对象,创建该对象,
* @author admin
*/
public class SingLeton {
/**
* 类初始化时,不初始化这个对象(延时加载,真正用的时候在创建)
*/
private static SingLeton instance;
/**
* 私有构造器只供自己使用
*/
private SingLeton() {
}
/**
* 方法同步,效率低
* @return
*/
public static synchronized SingLeton getInstance(){
if(null==instance){
instance =new SingLeton();
}
return instance;
}
}
静态内部类:
java
复制代码
/**
* ͨ 通过静态内部类实现単例模式
* 这种方法:线程安全,调用效率高,并且实现了延时加载
* @author admin
*/
public class SingLeton {
/**
* 只有调用getInstance()方法时,这个类才会被加载
* @author admin
*/
private static class SingLetonInstance{
private static final SingLeton instance=new SingLeton();
}
private SingLeton() {
}
/**
* 方法没有同步,效率高!
* @return
*/
public static SingLeton getInstance(){
return SingLetonInstance.instance;
}
}
枚举单例
java
复制代码
/**
* 枚举式实现単例模式!(没有延时加载)
* 可以天然防止反射和反序列化漏洞
* @author admin
*/
public enum SingLeton{
/**
* 这个枚举元素,本身就是単例对象
*/
Instance;
/**
* 可以添加自己需要的操作
*/
public void SingLeton(){
}
}
双重检查锁
java
复制代码
/**
* 通过双重检查锁实现単例模式
* 线程安全,可以延时加载。建议使用
* @author admin
*/
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
}
public Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
接下来给大家讲一下如何通过反射和反序列化破解单例?如何防止反射和反序列化破解单例??
反射如何破解单例?
懒汉式单例类
java
复制代码
/**
* 单利设计模式
* 懒汉式
* @author admin
*/
public class SingLeton implements Serializable{
/**
* 类初始化时,不初始化这个对象(延时加载,真正用的时候在创建)
*/
private static SingLeton instance;
private SingLeton() {
}
/**
* 方法同步,效率低
* @return
*/
public static synchronized SingLeton getInstance(){
if(null==instance){
instance =new SingLeton();
}
return instance;
}
}
通过反射破解单例
java
复制代码
public static void main(String[] args) throws Exception {
/**
* 反射如何破解単例(通过直接调用私有空构造器)
*/
Class<SingLeton> clazz=(Class<SingLeton>) Class.forName("com.reyco.design.singLeton.SingLeton");
Constructor<SingLeton> c=clazz.getDeclaredConstructor(null);
//忽略检查
c.setAccessible(true);
SingLeton s1=c.newInstance();
SingLeton s2=c.newInstance();
//比较是不是同一个对象
System.out.println(s1==s2);
}
如何防止反射破解单例?
java
复制代码
/**
* 单利设计模式
* 懒汉式--->(如何防止反射和反序列化漏洞)
* @author admin
*/
public class SingLeton implements Serializable{
/**
* 类初始化时,不初始化这个对象(延时加载,真正用的时候在创建)
*/
private static SingLeton instance;
private SingLeton() {
//防止反射漏洞:如果有对象了,再次调用,就抛出RuntimeException异常.
if(null!=instance){
throw new RuntimeException();
}
}
/**
* 方法同步,效率低
* @return
*/
public static synchronized SingLeton getInstance(){
if(null==instance){
instance =new SingLeton();
}
return instance;
}
}
如何通过反序列化破解单例?
java
复制代码
/**
* 反序列化如何破解単例(除了枚举)
* @author admin
*/
public static void main(String[] args) throws Exception {
/**
* 反序列化如何破解単例
*/
SingLeton s1 = SingLeton.getInstance();
//序列化
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("E:/test/09.txt"));
oos.writeObject(s1);
oos.flush();
oos.close();
//反序列化
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("E:/test/09.txt"));
SingLeton s2=(SingLeton) ois.readObject();
System.out.println(s1==s2);
}
如何防止反序列化破解单例?
- 可以通过定义readResolve()方法防止获取不同对象。反序列化时,如果对象所在类定义了readResolve()方法,则直接返回此方法指定的对象,而不需要单独创建对象.
java
复制代码
/**
* 单利设计模式
* 懒汉式--->(如何防止反射和反序列化漏洞)
* @author admin
*/
public class SingLeton implements Serializable{
/**
* 类初始化时,不初始化这个对象(延时加载,真正用的时候在创建)
*/
private static SingLeton instance;
private SingLeton() {
}
/**
* 方法同步,效率低
* @return
*/
public static synchronized SingLeton getInstance(){
if(null==instance){
instance =new SingLeton();
}
return instance;
}
/**
* 防止反序列化漏洞(反序列化,如果定义了readResolve方法,则直接返回此方法指定的对象,而不需要单独创建对象)
* @return
* @throws Exception
*/
public Object readResolve()throws Exception{
return instance;
}
}