单例模式singleton

一、单例模式作用:

保证一个类只有一个实例,提供一个获取该实例的全局方法。

二、分类:

饿汉式、懒汉式、双重检测锁、枚举、静态内部类

三、选用:

需要延时加载时:静态内部类 优于 懒汉式
不需要延时加载:枚举 优于 饿汉式


饿汉式

/**
 * eager:
 * advantage: 1.thread security 2.high call efficiency
 * disadvantage: 2.not a lazy loading
 * 注意:
 * static修饰的变量,在类装载时,就被初始化。
 * 虚拟机保证只会载一次该类。
 * 所以不会发生并发访问的问题,是线程安全的。
 */
public class Eager {

    private static Eager eager = new Eager();

    private Eager(){}

    public static Eager getInstance(){
        return eager;
    }

}

懒汉式

/**
 * lazy:
 * 特点:1.线程安全。2.调用效率低。3.懒加载
 * 注意:synchronized一定要有,否则在并发访问时,可能出现多个实例的情况。
 */
public class Lazy {

    private static Lazy lazy;

    private Lazy(){}

    public static synchronized Lazy getInstance() {
        if (lazy == null) {
            lazy = new Lazy();
        }
        return lazy;
    }
}

(双重检测锁:由于编译优化和JVM底层内部模型的原因,可能会出现问题)

静态内部类

/**
 * 静态内部类:
 * 特点:1、线程安全。2、调用效率高。3、懒加载
 * 注意:只有真正调用getInstance()时,才会加载静态内部类。加载类时是线程安全的。
 */
public class StaticInner {

    private static class si{
        private static StaticInner instance = new StaticInner();
    }

    private StaticInner(){}

    public static StaticInner getInstance(){
        return si.instance;
    }
}

枚举

/**
 * 枚举:
 * 特点:1、线程安全。2、调用效率高。3、不能懒加载
 * 注意:枚举本身就是单例模式。有JVM从根本上提供保障。可以直接避免反射和反序列化的漏洞。
 */
public enum EnumSingleton {

    INSTANCE;

    // 添加自己需要的方法,去操作单例对象。
    public void operation(){

    }
}

四、测试

反射破解单例
反序列化破解单例

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class TestSingleton {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
        System.out.println(Eager.getInstance() == Eager.getInstance());
        System.out.println(EnumSingleton.INSTANCE == EnumSingleton.INSTANCE);

        //反射破解单例
        Class<Eager> aClass = (Class<Eager>) Class.forName("com.hui.Eager");
        Constructor<Eager> c = aClass.getDeclaredConstructor(null);
        c.setAccessible(true);
        System.out.println(c.newInstance() == c.newInstance());

        //反序列化破解单例
        EagerAdvanced ea = EagerAdvanced.getInstance();
        FileOutputStream fos = new FileOutputStream("d:/a.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(ea);
        oos.close();
        fos.close();
        FileInputStream fis = new FileInputStream("d:/a.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
        EagerAdvanced ea1 = (EagerAdvanced) ois.readObject();
        System.out.println(ea == ea1);
        ois.close();
        fis.close();
    }
}


五、饿汉式(反射、反序列化)

/**
 * eager:
 * 特点:1.线程安全。2调用效率高。3.不能懒加载
 * 注意:
 * static修饰的变量,在类装载时,就被初始化。
 * 虚拟机保证只会载一次该类。
 * 所以不会发生并发访问的问题,是线程安全的。
 */
public class EagerAdvanced implements Serializable {

    private static EagerAdvanced eager = new EagerAdvanced();

    private EagerAdvanced(){
        if (eager != null) {
            throw new RuntimeException(); //避免反射破解单例
        }
    }

    public static EagerAdvanced getInstance(){
        return eager;
    }

    /**
     * 当一个类中有readResolve()时,再发生反序列化的情况下。
     * 会调用readResolve()直接返回实例,不会再重新生成对象。
     * @return
     * @throws ObjectStreamException
     */
    private Object readResolve() throws ObjectStreamException {
        return eager; //避免反序列化破解单例
    }

}

六、测试多线程环境下,几种单例模式的效率

public class TestEfficiency {

    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        int threadNum = 10;
        CountDownLatch countDownLatch = new CountDownLatch(threadNum);

        for(int i = 0; i < threadNum; i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 100000; i++){
                        Object instance = Lazy.getInstance();
                    }
                    countDownLatch.countDown();
                }
            }).start();
        }

        countDownLatch.await();
        long end = System.currentTimeMillis();
        System.out.println("总耗时:" + (end - start));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值