设计模式:创建者模式-单例模式

创建者模式:

创建型模式的主要关注点是"怎样创建对象? ”,它的主要特点是"将对象的创建与使用分离。

这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。

创建型模式分为:

  • 单例模式
  • 工厂方法模式
  • 抽象工厂模式
  • 原型模式
  • 建造者模式

单例模式:

单例模式(Singleton Pattern)是java中最简单的设计模式之一,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到**一个单一的类,该负责创建自己的对象,同时确保只有单个对象被创建。**这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式的结构:

单例模式主要有以下角色:

  • 单例类。只能创建一个实例的类
  • 访问类。使用单例类
单例模式的实现:

单例涉及模式类分为两种:

  • 饿汉式:类加载时,单实例对象就被创建
  • 懒汉式:类加载时,不会创建单实例对象,而是在首次使用该对象时才会创建
饿汉式实现方式:
静态变量实现方式:
public class Singleton {
    //私有构造方法
    private Singleton() {}

    //在本类中创建本类对象
    private static final Singleton instance = new Singleton();

    //提供一个公共的访问方式
    public static Singleton getInstance(){
        return instance;
    }
}

该方式在成员位置声明singleton类型的静态变量,并创建singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。

静态代码块实现方式:
public class Singleton {
    //私有构造方法
    private Singleton() {
    }

    //声明Singleton类型的变量
    private static final Singleton instance;

    //在静态代码块中进行赋值
    static {
        instance = new Singleton();
    }

    //提供一个公共的访问方式
    public static Singleton getInstance() {
        return instance;
    }
}

该方式在成员位置声明singleton类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。所以和饿汉式的静态变量实现基本上一样,当然该方式也存在内存浪费问题。

懒汉式实现方法:
线程不安全:
public class Singleton {
    //私有构造方法
    private Singleton() {
    }

    //声明Singleton类型的变量
    //只是声明了一个该类型的变量,并没有给该变量赋值
    private static Singleton instance;

    //提供一个公共的访问方式
    public static Singleton getInstance() {
        //判断instance是否为null,如果为null,说明还没有创建Singleton类的对象
        //如果没有,创建并返回。如果没有就直接返回
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
线程安全:
public class Singleton {
    //私有构造方法
    private Singleton() {
    }

    //声明Singleton类型的变量
    //只是声明了一个该类型的变量,并没有给该变量赋值
    private static Singleton instance;

    //提供一个公共的访问方式
    public static synchronized Singleton getInstance() {
        //判断instance是否为null,如果为null,说明还没有创建Singleton类的对象
        //如果没有,创建并返回。如果没有就直接返回
        if (instance == null) {
            //线程一等待,线程二拿到执行权,也会进入该判断中
            instance = new Singleton();
        }
        return instance;
    }
}
双重检查锁:

再来讨论一下懒汉模式中加锁的问题,对于getInstance ()方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的实现模式:双重检查锁模式

public class Singleton {
    //私有构造方法
    private Singleton() {
    }

    //声明Singleton类型的变量
    //只是声明了一个该类型的变量,并没有给该变量赋值
    //volatile 关键字:可以保证可见性和有序性
    private static volatile Singleton instance;

    //提供一个公共的访问方式
    public static Singleton getInstance() {
        //如果instance的值不为null,不需要抢占锁,直接放回对象
        if (instance == null) {
            //给这个类加锁
            synchronized (Singleton.class) {
                //第二次判断
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。要解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile关键字,volatile关键字可以保证可见性和有序性。

添加volatile关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题。

静态内部类:

静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中,是不会加载静态内部类的,只有内部类的 属性/方法 被调用时才会被加载,并初始化其静态属性。静态属性由于被 static修饰,保证只被实例化一次,并且严格保证实例化顺序。

public class Singleton {
    //私有构造方法
    private Singleton() {
    }

    //静态内部类, JVM 在加载外部类的过程中,是不会加载静态内部类的,只有内部类的 属性/方法 被调用时才会被加载,并初始化其静态属性
    private static final class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    //提供一个公共的访问方式
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

说明:

第一次加载singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载 SingletonHolder并初始化INSTANCE,这样不仅能确保线程安全,也能保证singleton类的唯一性。

小结:静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。

枚举方式:

枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。

public enum Singleton {
    INSTANCE;
}
存在的问题:
破环单例模式:

使上面定义的单例类(singleton)可以创建多个对象,枚举方式除外。有两种方式,分别是序列化和反射。

序列化反序列化破:
public class test {
    public static void main(String[] args) {
        try {
            writeObject2File();
            readObjectFromFile();
            readObjectFromFile();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    //从文件中读取数据(对象)
    public static void readObjectFromFile() throws IOException, ClassNotFoundException {
        //创建输入流对象
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\deskBook\\a.txt"));
        //读取对象
        Singleton singleton = (Singleton) objectInputStream.readObject();
        System.out.println(singleton);
        //释放资源
        objectInputStream.close();
    }

    //向文件中写数据(对象)
    public static void writeObject2File() throws IOException {
        //获取Singleton对象
        Singleton singleton = Singleton.getInstance();
        //创建对象输出流对象
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\deskBook\\a.txt"));
        //写对象
        objectOutputStream.writeObject(singleton);
        //释放资源
        objectOutputStream.close();
    }
}


//===========================================输出结果:
com.design.single.Singleton@27973e9b
com.design.single.Singleton@312b1dae
破环了单例模式
通过反射:
public class test {
    public static void main(String[] args) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        //获取字节码对象
        Class<Singleton> clazz = Singleton.class;
        //获取无参构造方法对象
        Constructor<Singleton> constructor = clazz.getDeclaredConstructor();
        //取消访问检查
        constructor.setAccessible(true);

        //创建Singleton对象
        Singleton singleton = constructor.newInstance();
        Singleton singleton1 = constructor.newInstance();
        System.out.println("通过反射创建的对象是否是同一个对象:" + (singleton == singleton1));
    }
}

//===========================================输出结果:
通过反射创建的对象是否是同一个对象:false
解决问题:
序列化和反系列化方式破环单例模式的解决方法。

在Singleton类中添加readResolve() 方法,在反系列化时被反射调用,如果定义了这个方法,就直接方法该方法的值,如果没有定义就返回新new出来的对象。

public class Singleton {
    //私有构造方法
    private Singleton() {
    }

    //静态内部类, JVM 在加载外部类的过程中,是不会加载静态内部类的,只有内部类的 属性/方法 被调用时才会被加载,并初始化其静态属性
    private static final class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    //提供一个公共的访问方式
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

    //当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回。
    //在objectInputStream.readObject()中底层代码会判断是否有readResolve()这个方法,有就会自动调用该方法。
    public Object readResolve(){
        return SingletonHolder.INSTANCE;
    }
}
反射方法破解单例模式的解决方法:
public class Singleton {
    //定义一个标识符
    private static boolean flag = false;

    //私有构造方法
    private Singleton() {
        //加锁防止多线程问题
        synchronized (Singleton.class) {
            //判断flag的值是否为真,如果为真,说明不是第一次访问,直接抛出异常
            if (flag) {
                throw new RuntimeException("单例模式不能创建多个对象");
            }
            //将标识标记为true
            flag = true;
        }
    }

    //静态内部类, JVM 在加载外部类的过程中,是不会加载静态内部类的,只有内部类的 属性/方法 被调用时才会被加载,并初始化其静态属性
    private static final class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    //提供一个公共的访问方式
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

JDK中的Runtime类就是使用的单例模式。使用的是饿汉式-静态属性方法来实现的单例模式。

简单使用:

public class Test {
    public static void main(String[] args) throws IOException {
        //获取Runtime类的对象
        Runtime runtime = Runtime.getRuntime();
        //调用runtime的exec方法,参数是一个终端命令
        Process exec = runtime.exec("ifconfig");
        //使用Process对象的获取输入流方法
        InputStream inputStream = exec.getInputStream();
        byte[] arr = new byte[1024 * 1024 * 100];
        //读取数据
        int len= inputStream.read(arr);
        //将读取到的数据打印到控制台
        System.out.println(new String(arr,0,len,"GBK"));
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值