Java 实现单例模式的5种方式

1. 什么是单例模式

单例模式指的是在应用整个生命周期内只能存在一个实例。单例模式是一种被广泛使用的设计模式。他有很多好处,能够避免实例对象的重复创建,减少创建实例的系统开销,节省内存。

2. 如何实现单例模式

1. 饿汉模式

所谓饿汉模式就是立即加载,一般情况下再调用getInstancef方法之前就已经产生了实例,也就是在类加载的时候已经产生了。这种模式的缺点很明显,就是占用资源,当单例类很大的时候,其实我们是想使用的时候再产生实例。因此这种方式适合占用资源少,在初始化的时候就会被用到的类。

/**
 * 单例模式:饿汉模式
 */
public class SingletonHungary {

    private static SingletonHungary singletonHungary = new SingletonHungary();

    private SingletonHungary() {
    }

    public static SingletonHungary getInstance() {
        return singletonHungary;
    }

}

2. 枚举式饿汉模式

/**
 * 饿汉模式:枚举
 */
public enum SingletonEnumHungary {
    INSTANCE;

    public void someThing() {
        System.out.println("枚举方法实现单例模式");
    }
}

2. 懒汉模式

不带锁懒汉模式

/**
 * 设计模式:懒汉模式
 */
public class SingletonLazy {

    private static SingletonLazy singletonLazy = null;

    private SingletonLazy() {
    }

    public static SingletonLazy getInstance() {
        if (null == singletonLazy) {
            singletonLazy = new SingletonLazy();
        }
        return singletonLazy;
    }

}

带锁的懒汉模式

/**
 * 加上锁的懒汉模式
 */
public class SingletonLazyThread {

    private static SingletonLazyThread singletonLazyThread;

    private SingletonLazyThread() {

    }

    public static SingletonLazyThread getInstance() {
        try {
            if (null == singletonLazyThread) {
                Thread.sleep(1000);
                synchronized (SingletonLazyThread.class) {
                    if (null == singletonLazyThread) {
                        singletonLazyThread = new SingletonLazyThread();
                    }
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return singletonLazyThread;
    }

}

3. 静态内部类

/**
 * 静态内部类
 */
public class SingletonStaticInner {

    public SingletonStaticInner() {
    }

    private static class SingletonInner {
        private static SingletonStaticInner singletonStaticInner = new SingletonStaticInner();
    }

    public static SingletonStaticInner getInstance() {
        try {
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return SingletonInner.singletonStaticInner;
    }

}

可以看到使用这种方式我们没有显式的进行任何同步操作,那他是如何保证线程安全呢?和饿汉模式一样,是靠JVM保证类的静态成员只能被加载一次的特点,这样就从JVM层面保证了只会有一个实例对象。那么问题来了,这种方式和饿汉模式又有什么区别呢?不也是立即加载么?实则不然,加载一个类时,其内部类不会同时被加载。一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生。

可以说这种方式是实现单例模式的最优解。

4. 静态代码块

这里提供了静态代码块实现单例模式。这种方式和第一种类似,也是一种饿汉模式。

/**
 * 懒汉模式:静态代码块
 */
public class SingletonStaticBlock {

    private static SingletonStaticBlock singletonStaticBlock;

    static {
        singletonStaticBlock = new SingletonStaticBlock();
    }

    public static SingletonStaticBlock getInstance() {
        return singletonStaticBlock;
    }

}

5. 序列化与反序列化

/**
 * 懒汉模式:序列化和反序列化
 * 使用匿名内部类实现单例模式,在遇见序列化和反序列化的场景,得到的不是同一个实例
 * 解决这个问题是在序列化的时候使用readResolve方法,即去掉注释的部分
 */
public class SingletonStaticInnerSerialize implements Serializable {

    private static final long serialVersionUID = 1L;

    private static class InnerClass {
        private static SingletonStaticInnerSerialize innerSerialize = new SingletonStaticInnerSerialize();
    }

    public static SingletonStaticInnerSerialize getInstance() {
        return InnerClass.innerSerialize;
    }

    protected Object readResolve() {
        System.out.println("调用了readResolve方法");
        return InnerClass.innerSerialize;
    }

}

测试代码:

/**
 * 测试静态内部类
 */
public class TestSingletonStaticInnerSerializable {

    public static void main(String[] args) {
        try {
            SingletonStaticInnerSerialize serialize = SingletonStaticInnerSerialize.getInstance();
            System.out.println(serialize.hashCode());
            // 序列化
            FileOutputStream fo = new FileOutputStream("tem");
            ObjectOutputStream oos = new ObjectOutputStream(fo);
            oos.writeObject(serialize);
            oos.close();
            fo.close();
            // 反序列化
            FileInputStream fi = new FileInputStream("tem");
            ObjectInputStream ois = new ObjectInputStream(fi);
            SingletonStaticInnerSerialize serialize1 = (SingletonStaticInnerSerialize) ois.readObject();
            ois.close();
            fi.close();
            System.out.println(serialize1.hashCode());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}

可以看到:

865113938
1078694789


结果表明的确是两个不同的对象实例,违背了单例模式,那么如何解决这个问题呢?解决办法就是在反序列化中使用readResolve()方法,将上面的注释代码去掉,再次运行:

865113938
调用了readResolve方法
865113938


问题来了,readResolve()方法到底是何方神圣,其实当JVM从内存中反序列化地"组装"一个新对象时,就会自动调用这个 readResolve方法来返回我们指定好的对象了, 单例规则也就得到了保证。readResolve()的出现允许程序员自行控制通过反序列化得到的对象。

转载地址:https://blog.csdn.net/u014672511/article/details/79774847

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值