设计模式之单例模式

概念:单例模式是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种模式方法。

饿汉式单例:

package com.example.SingDemo;

/**
 * description
 * <p>
 * 饿汉式单例
 * 饿汉式单例是指在方法调用前,实例就已经创建好了。下面是实现代码:
 * @author slliao
 * @date 2019/5/5 14:12.
 */
public class HungryMySingleton {
    private static HungryMySingleton instance = new HungryMySingleton();
    private HungryMySingleton(){

    }
    public static HungryMySingleton getInstance(){
        return instance;
    }
}

测试

package com.example.SingDemo;

/**
 * description
 * <p>
 *
 * @author slliao
 * @date 2019/5/5 14:15.
 */
public class HungryMyThread extends Thread {
    @Override
    public void run() {
        System.out.println(HungryMySingleton.getInstance().hashCode());
    }

    public static void main(String[] args) {
        HungryMyThread[] mts = new HungryMyThread[10];
        int len=mts.length;
        for (int i=0;i<len;i++){
            mts[i]=new HungryMyThread();
        }
        for ( int j=0;j<len;j++){
            mts[j].start();
        }
    }
}

结果为在这里插入图片描述从运行结果可以看出实例变量额hashCode值一致,这说明对象是同一个,饿汉式单例实现了。

缺点:饿汉模式天生是线程安全的,使用时没有延迟
优点:启动时即创建实例,启动慢,有可能造成资源浪费

懒汉式单例

package com.example.SingDemo;

/**
 * description
 * <p>
 *  懒汉式单例
 *  懒汉式单例是指在方法调用获取实例时才创建实例,相对于饿汉式显得“不急迫”,所以被叫做“懒汉模式”
 * @author slliao
 * @date 2019/5/5 14:38.
 */
public class FullMySingleton {
    private static FullMySingleton instance =null;
    public static FullMySingleton getInstance(){
        try {
            if (instance == null){
                //新建实例对象之前做一些准备工作,停顿一段时间
                Thread.sleep(200);
                instance = new FullMySingleton();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return instance;
    }
}

测试

package com.example.SingDemo;

/**
 * description
 * <p>
 *
 * @author slliao
 * @date 2019/5/5 14:44.
 */
public class FullMyThread extends Thread {
    @Override
    public void run(){
        System.out.println(FullMySingleton.getInstance().hashCode());
    }

    public static void main(String[] args) {
        FullMyThread[] mts = new FullMyThread[10];
        int length = mts.length;
        for (int i=0;i<length;i++){
            mts[i] = new FullMyThread();
        }
        for (int j=0;j<length;j++){
            mts[j].start();
        }
    }
}

结果为
在这里插入图片描述从运行结果来看,这种懒汉式单例模式无法保证实例唯一。

继续改进,方法中声明synchronized

package com.example.SingDemo;

/**
 * description
 * <p>
 *  同步方法块
 *  方法中声明synchronized关键字
 * @author slliao
 * @date 2019/5/5 14:55.
 */
public class FullMySingletonMethod {
    private static FullMySingletonMethod instance =null;
    //对方法加锁
    public  synchronized static FullMySingletonMethod getInstance(){
        try {
            if (instance == null){
                //新建实例对象之前做一些准备工作,停顿一段时间
                Thread.sleep(200);
                instance = new FullMySingletonMethod();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return instance;
    }
}

测试

package com.example.SingDemo;

/**
 * description
 * <p>
 *
 * @author slliao
 * @date 2019/5/5 14:44.
 */
public class FullMyThreadMethod extends Thread {
    @Override
    public void run(){
        System.out.println(FullMySingletonMethod.getInstance().hashCode());
    }

    public static void main(String[] args) {
        FullMyThreadMethod[] mts = new FullMyThreadMethod[10];
        int length = mts.length;
        for (int i=0;i<length;i++){
            mts[i] = new FullMyThreadMethod();
        }
        for (int j=0;j<length;j++){
            mts[j].start();
        }
    }
}

结果为
在这里插入图片描述从结果中可以看到,保证了实例对象的唯一性 。但是,对整个方法进行加锁后,会发现执行效率变低了(加锁的范围越大,效率越低)。

继续改进,对关键代码块进行加锁

package com.example.SingDemo;

/**
 * description
 * <p>
 *  同步代码块
 *  代码中声明synchronized关键字
 * @author slliao
 * @date 2019/5/5 14:55.
 */
public class FullMySingletonCode {
    private static FullMySingletonCode instance =null;
    public  static FullMySingletonCode getInstance(){
        try {
           //对部分代码块加锁
           synchronized (FullMySingletonCode.class){
                if (instance == null){
                    //新建实例对象之前做一些准备工作,停顿一段时间
                    Thread.sleep(200);
                    instance = new FullMySingletonCode();
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return instance;
    }
}

测试

package com.example.SingDemo;

/**
 * description
 * <p>
 *
 * @author slliao
 * @date 2019/5/5 14:44.
 */
public class FullMyThreadCode extends Thread {
    @Override
    public void run(){
        System.out.println(FullMySingletonCode.getInstance().hashCode());
    }

    public static void main(String[] args) {
        FullMyThreadCode[] mts = new FullMyThreadCode[10];
        int length = mts.length;
        for (int i=0;i<length;i++){
            mts[i] = new FullMyThreadCode();
        }
        for (int j=0;j<length;j++){
            mts[j].start();
        }
    }
}

结果为
在这里插入图片描述
保证了实例对象的唯一性 。

静态内置类实现单例模式

package com.example.SingDemo;

/**
 * description
 * <p>
 *使用静态内部类来实现单例,感觉和饿汉式差不多,在初始化静态变量时只创建一次对象而已,之后使用的都是同一对象
 * @author slliao
 * @date 2019/5/5 14:12.
 */
public class InternalMySingleton {
    private static class InternalMethod{
        private static InternalMySingleton instance = new InternalMySingleton();
    }
    public static InternalMySingleton getInstance(){
        return InternalMethod.instance;
    }
}

测试

package com.example.SingDemo;

/**
 * description
 * <p>
 *
 * @author slliao
 * @date 2019/5/5 14:15.
 */
public class InternalMyThread extends Thread {
    @Override
    public void run() {
        System.out.println(InternalMySingleton.getInstance().hashCode());
    }

    public static void main(String[] args) {
        InternalMyThread[] mts = new InternalMyThread[10];
        int len=mts.length;
        for (int i=0;i<len;i++){
            mts[i]=new InternalMyThread();
        }
        for ( int j=0;j<len;j++){
            mts[j].start();
        }
    }
}

结果
在这里插入图片描述但是当使用静态内部类在遇到序列化对象时,默认的方式运行得到的结果就是多例的

package com.example.SingDemo;

import java.io.Serializable;

/**
 * description
 * <p>
 *序列化与反序列化的单例模式实现
 * @author slliao
 * @date 2019/5/5 14:12.
 */
public class InternalMySingletonSerializable implements Serializable {
    private static final long serialVersionUID = 1L;
    private static class InternalMethod{
        private static InternalMySingletonSerializable instance = new InternalMySingletonSerializable();
    }
    public static InternalMySingletonSerializable getInstance(){
        return InternalMethod.instance;
    	}
    }

测试

package com.example.SingDemo;

import java.io.*;

/**
 * description
 * <p>
 *
 * @author slliao
 * @date 2019/5/5 15:39.
 */
public class InputAndOutForSingleton {
    public static void main(String[] args) {
        InternalMySingletonSerializable singleton = InternalMySingletonSerializable.getInstance();
        File file = new File("SerializableSingleton.txt");

        try {
            FileOutputStream fos = new FileOutputStream(file);
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(singleton);
            fos.close();
            oos.close();
            System.out.println(singleton.hashCode());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            FileInputStream fis = new FileInputStream(file);
            ObjectInputStream ois = new ObjectInputStream(fis);
            InternalMySingletonSerializable rSingleton = (InternalMySingletonSerializable) ois.readObject();
            fis.close();
            ois.close();
            System.out.println(rSingleton.hashCode());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
}

结果为:
在这里插入图片描述
从结果可以看出序列号对象的hashCode和反序列化后得到的对象的hashCode值不一样,说明反序列化后返回的对象是重新实例化的,单例被破坏了。
解决办法就是在反序列化的过程中使用readResolve()方法。在类中加入readResolve方法。

package com.example.SingDemo;

import java.io.Serializable;

/**
 * description
 * <p>
 *序列化与反序列化的单例模式实现
 * @author slliao
 * @date 2019/5/5 14:12.
 */
public class InternalMySingletonSerializable implements Serializable {
    private static final long serialVersionUID = 1L;
    private static class InternalMethod{
        private static InternalMySingletonSerializable instance = new InternalMySingletonSerializable();
    }
    public static InternalMySingletonSerializable getInstance(){
        return InternalMethod.instance;
    }

    /**
     * 该方法在反序列化时会被调用,反序化时就会产生一个克隆的对象,这就打破了单例的规则
     * 那就是如果被反序列化的对象的类存在readResolve这个方法,
     * 他会调用这个方法,作为返回值,并且无视掉反序列化的值,即使那个字节码已经被解析。 
     * @return
     */
    protected Object readResolve(){
        System.out.println("调用了readResolve方法!");
        return InternalMethod.instance;
    }
}

结果为
在这里插入图片描述

使用双检查锁机制。

双重检查锁

  package com.example.SingDemo;
    
    /**
     * description
     * <p>
     *  懒汉式单例
     *  双检查锁机制
     * @author slliao
     * @date 2019/5/5 14:38.
     */
    public class FullMySingletonDouble {
        /**
         * 使用volatile关键字保其可见性
         */
        volatile private static FullMySingletonDouble instance =null;
        public static FullMySingletonDouble getInstance(){
            try {
                if (instance == null){
                    //新建实例对象之前做一些准备工作,停顿一段时间
                    Thread.sleep(200);
                    synchronized (FullMySingletonDouble.class){
                        if (instance ==null){
                            instance = new FullMySingletonDouble();
                        }
                    }
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            return instance;
        }
    }

测试

package com.example.SingDemo;

/**
 * description
 * <p>
 *
 * @author slliao
 * @date 2019/5/5 14:44.
 */
public class FullMyThreadDouble extends Thread {
    @Override
    public void run(){
        System.out.println(FullMySingletonDouble.getInstance().hashCode());
    }

    public static void main(String[] args) {
        FullMyThreadDouble[] mts = new FullMyThreadDouble[100];
        int length = mts.length;
        for (int i=0;i<length;i++){
            mts[i] = new FullMyThreadDouble();
        }
        for (int j=0;j<length;j++){
            mts[j].start();
        }
    }
}

结果为

在这里插入图片描述
声明变量时使用了volatile关键字来保证其线程间的可见性;在同步代码块中使用二次检查,保证不被重复的实例化。集合其二者,这种实现方式既保证了其高效性,也保证了其线程安全性

懒汉式总结:Lazy load,用到才加载,非线程安全。如何保证线程安全呢:

(1) synchronized getInstance()。

(2)双重检查加锁(volatile)。

枚举实现单例模式

枚举单例模式

package com.example.SingDemo;

/**
 * description
 * <p>
 * 使用枚举来实现单例
 * @author slliao
 * @date 2019/5/5 16:13.
 */
public class EnumSingleton {
    public static EnumSingleton getInstance(){
        return SingEnum.instance.getSingleton();
    }

    enum SingEnum {
        instance;
        private EnumSingleton singleton;

        SingEnum(){
            singleton = new EnumSingleton();
        }

        private EnumSingleton getSingleton(){
            return singleton;
        }
    }

}

测试

package com.example.SingDemo;

/**
 * description
 * <p>
 *
 * @author slliao
 * @date 2019/5/5 14:15.
 */
public class EnumMyThread extends Thread {
    @Override
    public void run() {
        System.out.println(EnumSingleton.getInstance().hashCode());
    }

    public static void main(String[] args) {
        EnumMyThread[] mts = new EnumMyThread[10];
        int len=mts.length;
        for (int i=0;i<len;i++){
            mts[i]=new EnumMyThread();
        }
        for ( int j=0;j<len;j++){
            mts[j].start();
        }
    }
}

结果
在这里插入图片描述
由于枚举实例的创建默认就是线程安全的,你不需要担心双检锁问题。
在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject等方法。
普通的Java类的反序列化过程中,会通过反射调用类的默认构造函数来初始化对象。所以,即使单例中构造函数是私有的,也会被反射给破坏掉。由于反序列化后的对象是重新new出来的,所以这就破坏了单例。但是,枚举的反序列化并不是通过反射实现的。所以,也就不会发生由于反序列化导致的单例破坏问题。
详细介绍可以看看这个 https://www.cnblogs.com/z00377750/p/9177097.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值