常用设计模式之单例模式

单例模式(Singleton Pattern):在应用中保证一个类只有一个实例,并且提供了该该实例的全局访问点

主要解决一个全局使用的类频繁的创建与销毁

单例模式分为懒汉式和饿汉式

懒汉式:每次获取实例都会判断是否需要创建实例,浪费时间。当没有人使用时则不会创建实例。以时间换空间。懒汉模式在不加同步锁时是线程不安全

饿汉式:在类初始化的时候就会创建实例,每次获取时不进行判断。以空间换时间。是线程安全

单例模式的实现

懒汉模式

public class LazySingleton {

    private static LazySingleton instance;

    private LazySingleton(){}

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

}



    public static void main(String[] args) throws InterruptedException {
        LazySingleton instance1 = LazySingleton.getInstance();
        LazySingleton instance2 = LazySingleton.getInstance();
        System.out.println(instance1==instance2); // 输出true
    }

当多个线程同时访问时可能会返回多个实例

    public static LazySingleton getInstance() throws InterruptedException {
        if (instance == null) {
            Thread.sleep(8000);
            instance = new LazySingleton();
        }
        return instance;
    }


    public static void main(String[] args) {
        new Thread(() -> {
            try {
                LazySingleton instance = LazySingleton.getInstance();
                System.out.println(instance); // 输出cn.pattern.LazySingleton@674827d5
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                LazySingleton instance = LazySingleton.getInstance();
                System.out.println(instance); // 输出cn.pattern.LazySingleton@6e5802d8
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }

可以添加同步锁解决,同步锁只允许一个线程通过,其他线程会被阻塞,造成一定性能损耗

    public static LazySingleton getInstance() {
        if (instance == null) {
            synchronized(LazySingleton.class) {
                if (instance == null) {
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }

在字节码的层面上看简单new一个对象对应的指令如下

         0: new           #2                  // class cn/pattern/T
         3: dup
         4: invokespecial #3                  // Method cn/pattern/T."<init>":()V
         7: astore_1
         8: return

new: 会在堆内存中分配一块空间,并将该空间的引用压入操作数栈的栈顶

dup:将栈顶的引用复制一份

invokespecial:调用初始化方法进行初始化,此时操作数栈顶的引用会出栈,作为this参数传递给构造器

astore_1:把栈顶的引用出栈赋值到变量

return:终止该方法执行

有一些指令调换执行顺序并不会产生影响,如invokespecial和astore_1,为了提升性能JIT,CPU可能会对指令进行重排,可能会先执行astore_1再执行invokespecial。

指令重排后当一个线程执行astore_1后另一个线程进入发现已经有引用就会直接返回,可能会造成空指针异常

所以加上volatile关键字阻止指令重排,防止出现上述情况

public class LazySingleton {

    private volatile static LazySingleton instance;

    private LazySingleton() throws Exception {
        if (instance != null) {
            throw new Exception("已存在实例");
        }
    }

    public static LazySingleton getInstance() throws Exception {
        if (instance == null) {
            synchronized(LazySingleton.class) {
                if (instance == null) {
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }

}

 

饿汉模式

public class HungerSingleton {

    private static HungerSingleton instance = new HungerSingleton();

    private HungerSingleton(){}

    public static HungerSingleton getInstance() {
        return instance;
    }

}

使用static通过jvm的类加载机制保证类的唯一性。

当该类还有别的静态方法instance会在别的静态方法被调用时就初始化,可以通过内部类的方式解决(懒汉模式)

public class InnerSingleton {

    private InnerSingleton(){}

    public static InnerSingleton getInstance() {
        return Singleton.instance;
    }

    private static class Singleton {
        public static InnerSingleton instance = new InnerSingleton();
    }
}

虽然构造器被定义成了private,但是通过反射的方式还是可以访问

    public static void main(String[] args) throws ClassNotFoundException,NoSuchMethodException, IllegalAccessException, InvocationTargetException,     InstantiationException {
        Class cls = Class.forName("cn.pattern.LazySingleton");
        Constructor constructor = cls.getDeclaredConstructor();
        constructor.setAccessible(true);
        LazySingleton lazySingleton = (LazySingleton) constructor.newInstance();
        System.out.println(lazySingleton);  // 输出cn.pattern.LazySingleton@1b6d3586
    }

而且通过RPC调用服务,序列号传参还是会生产新的对象,我们实现序列化接口测试一下

public class InnerSingleton implements Serializable {

    private static final long serialVersionUID = 1L;

    private InnerSingleton() {
        if (Singleton.instance != null) {
            throw new RuntimeException("单例对象已存在");
        }
    }

    public static InnerSingleton getInstance() {
        return Singleton.instance;
    }

    private static class Singleton {
        public static InnerSingleton instance = new InnerSingleton();
    }
}
    public static void main(String[] args) {
        InnerSingleton instance = InnerSingleton.getInstance();
        // 序列化
//        try(ObjectOutputStream oot = new ObjectOutputStream(new FileOutputStream("SerializableTest"))) {
//            oot.writeObject(instance);
//        }catch (Exception e) {
//            e.printStackTrace();
//        }

        // 反序列化
        try(ObjectInputStream oi = new ObjectInputStream(new FileInputStream("SerializableTest"))) {
            InnerSingleton innerSingleton = (InnerSingleton) oi.readObject();
            System.out.println(instance==innerSingleton); // 输出false
        }catch (Exception e){
            e.printStackTrace();
        }
    }

可以添加readObject()方法来解决反序列化问题

public class InnerSingleton implements Serializable {

    private static final long serialVersionUID = 1L;

    private InnerSingleton() {
        if (Singleton.instance != null) {
            throw new RuntimeException("单例对象已存在");
        }
    }

    public static InnerSingleton getInstance() {
        return Singleton.instance;
    }

    private Object readResolve() throws ObjectStreamException {
        return Singleton.instance;
    }

    private static class Singleton {
        public static InnerSingleton instance = new InnerSingleton();
    }
}

当单例对象实现Cloneable接口时,调用clone()方法也会返回新的实例,可以重写clone()方法,返回单例对象解决。

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值