网站所属权,站长之家官网登录入口,asp.net网站安全,定制网站多少钱文章目录 单例模式的结构如何控制只有一个对象呢怎么设计这个类的内部对象外部怎么访问 单例模式的主要有以下角色 单例模式的实现饿汉式 1#xff1a;静态变量饿汉式 2#xff1a;静态代码块懒汉式 1#xff1a;线程不安全懒汉式 2#xff1a;线程安全—方法级上锁懒汉式 … 文章目录 单例模式的结构如何控制只有一个对象呢怎么设计这个类的内部对象外部怎么访问 单例模式的主要有以下角色 单例模式的实现饿汉式 1静态变量饿汉式 2静态代码块懒汉式 1线程不安全懒汉式 2线程安全—方法级上锁懒汉式 3双重检查锁懒汉式 4静态内部类饿汉式 3枚举 存在的问题序列化破坏单例反射破坏单例 JDK 源码 - Runtime 类 单例模式Singleton Pattern是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式它提供了一种创建对象的最佳方式。 这种模式涉及到一个单一的类该类负责创建自己的对象同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式可以直接访问不需要实例化该类的对象。 所谓的单例模式保证某个类在程序中有且只有一个对象类比现实生活中的地球类只有一个地球对象
单例模式的结构
如何控制只有一个对象呢
我们创建一个对象是通过该类的构造方法进行创建对象我们不能让外部进行创建对象不然谁都能创建对象那么就保证不了单例那么我们的构造方法必须是private修饰来控制对象的创建也就是私有的构造方法外部不能创建对象那么这个创建唯一的对象的任务也就会在我们单例类来进行实现
怎么设计这个类的内部对象
首先我们知道在外部是创建不了对象的所以这个对象的类型肯定不能是成员的必须是静态的通过类调用因为成员变量的属性是通过我们的对象进行赋值我们只有一个对象如果是成员变量就先要有对象才能赋值而赋值了才有那个唯一的对象就出现了死循环
外部怎么访问
一般属性我们用private进行修饰为了实现其封装性直接通过公开的方法获取这个唯一的对象
单例模式的主要有以下角色
单例类。只能创建一个实例的类访问类。使用单例类
单例模式的实现
单例设计模式分类两种
饿汉式类加载就会导致该单实例对象被创建懒汉式类加载不会导致该单实例对象被创建而是首次使用该对象时才会创建
饿汉式 1静态变量
public class Singleton {//1定义静态成员变量在类加载时创建private static Singleton singletonnew Singleton();//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式让外界获取该对象public static Singleton getSingleton() {return singleton;}
}因为singleton是类成员变量在我 Singleton类加载过程中的初始化过程中进行创建对象赋值因为只会执行一次所以是天然的线程安全缺点singleton 对象是随着类的加载而创建的如果该对象很大却一直没有使用就会造成内存的浪费
饿汉式 2静态代码块
该方式在成员位置声明 Singleton 类型的静态变量而对象的创建是在静态代码块中也是随着类的加载而创建。
public class Singleton {//1定义静态成员变量在类加载时创建private static Singleton singleton;static {singletonnew Singleton();}//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式让外界获取该对象public static Singleton getSingleton() {return singleton;}
}
该方式和饿汉式的方式 1 基本一样所以该方式也存在内存浪费问题。
懒汉式 1线程不安全
该方式在成员位置声明 Singleton 类型的静态变量并没有进行对象的赋值操作那么什么时候赋值的呢
当调用 getInstance() 方法获取 Singleton 类的对象的时候才创建 Singleton 类的对象这样就实现了懒加载效果。
public class Singleton {//1定义静态成员变量在类加载时创建private static Singleton singleton;//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式让外界获取该对象public static Singleton getSingleton(){if(singletonnull){singletonnew Singleton();}return singleton;}
}测试
public class Client {public static void main(String[] args) {Thread thread1new Thread(()-{System.out.println(Singleton.getSingleton());});thread1.start();System.out.println(Singleton.getSingleton());}
}
//com.lsc.itheima.pattern.singleton.demo3.Singleton3d075dc0
//com.lsc.itheima.pattern.singleton.demo3.Singleton6b047ec发现不是同一个对象没有实现其单例的效果为什么不是线程安全的 比如我们的thread1线程调用了getSingleton进行if(singletonnull)判断进入了判断体然后我们的main线程抢占了CPU进行运行进行if(singletonnull)判断因为thread1线程并没有进行创建对象也进入了判断体故两个线程各自创建了对应的对象
懒汉式 2线程安全—方法级上锁
在懒汉式 1 的基础上使用 synchronized 关键字对getSingleton这个方法进行加锁
public class Singleton {//1定义静态成员变量在类加载时创建private static Singleton singleton;//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式让外界获取该对象public static synchronized Singleton getSingleton(){if(singletonnull){singletonnew Singleton();}return singleton;}
}但是该方法的执行效率特别低。 懒汉模式中加锁的问题对于 getSingleton 方法来说绝大部分的操作都是读操作读操作是线程安全的所以我们没必让每个线程必须持有锁才能调用该方法我们需要调整加锁的时机。由此也产生了一种新的实现模式双重检查锁模式 注意其实只有在初始化singleton的时候才会出现线程安全问题一旦初始化完成就不存在了。
懒汉式 3双重检查锁
public class Singleton {//1定义静态成员变量在类加载时创建private static Singleton singleton;//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式让外界获取该对象public static Singleton getSingleton(){//第一次判断如果singleton不为null不进入抢锁阶段直接返回实例if(singletonnull){synchronized (Singleton.class) {// 第二次判断,抢占到锁以后再次判断if (singleton null) {singleton new Singleton();}}}return singleton;}
}
双重检查锁模式是一种非常好的单例实现模式解决了单例、性能、线程安全问题上面的双重检测锁模式看上去完美无缺其实是存在问题在多线程的情况下可能会出现空指针问题出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
要解决双重检查锁模式带来空指针异常的问题只需要使用 volatile 关键字, volatile 关键字可以保证可见性和有序性。使用volatile关键字保证单例对象初始化不会被中断保证其他线程获得的对象一定是初始化完成的对象 比如现在t1线程执行到了初始化的实例对象正在执行new操作还没完全结束但是LazySingleTonnull然后t2线程执行到了判断唯一的对象是否为空,对于t2发现不为空就直接返回了但是返回的对象是没有完全初始化的所以需要加volatile就相当加了一层内存屏障保证其他线程返回的对象必须等操作完全结束才能执行return语句
public class Singleton {//1定义静态成员变量在类加载时创建// 声明Singleton类型的变量,使用volatile保证可见性和有序性private static volatile Singleton singleton;//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式让外界获取该对象public static Singleton getSingleton(){// 第一次判断,如果instance不为null,不需要抢占锁,直接返回对象if(singletonnull){synchronized (Singleton.class) {// 第二次判断,抢占到锁以后再次判断if (singleton null) {singleton new Singleton();}}}return singleton;}
}懒汉式 4静态内部类
静态内部类单例模式中实例由内部类创建由于 JVM 在加载外部类的过程中是不会加载静态内部类的只有内部类的属性/方法被调用时才会被加载并初始化其静态属性。静态属性由于被 static 修饰保证只被实例化一次并且严格保证实例化顺序。
public class Singleton {//2私有化构造方法private Singleton(){}// 定义一个静态内部类private static class SingletonHolder {// 在内部类中声明并初始化外部类的对象private static final Singleton singleton new Singleton();}// 提供公共的访问方式public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}第一次加载 Singleton 类时不会去初始化 singleton只有第一次调用 getInstance()虚拟机加载 SingletonHolder 类并初始化 singleton这样不仅能确保线程安全也能保证 Singleton 类的唯一性。
静态内部类单例模式是一种优秀的单例模式是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下保证了多线程下的安全并且没有任何性能影响和空间的浪费。
饿汉式 3枚举
首先枚举方式是饿汉式单例模式如果不考虑浪费内存空间的问题这是极力推荐的单例实现模式。
因为枚举类型是线程安全的并且只会装载一次设计者充分的利用了枚举的这个特性来实现单例模式。
枚举的写法非常简单而且枚举方式是所用单例实现中唯一一种不会被破坏的单例实现模式。
public class SingleTon {/*** 饿汉式枚举实现*/public enum Singleton {INSTANCE}
}存在的问题
有两种方式可以使上面定义的单例类可以创建多个对象枚举方式除外分别是序列化和反射。 枚举方式是利用了 Java 特性实现的单例模式不会被破坏其他实现方式都有可能会被破坏 序列化破坏单例
问题演示下面代码运行结果是false表明序列化和反序列化破坏了单例设计模式。
public class Client {public static void main(String[] args) throws IOException, ClassNotFoundException {writeObject2File();Singleton s1 readObjectFormFile();Singleton s2 readObjectFormFile();
// System.out.println(s1 s2); // false}//从文件读取数据public static Singleton readObjectFormFile() throws IOException, ClassNotFoundException {//创建对象输入流对象ObjectInputStream objectInputStream new ObjectInputStream(new FileInputStream(D:\\a.txt));//2.读取对象Singleton singleton (Singleton) objectInputStream.readObject();//3释放资源objectInputStream.close();return singleton;}//向文件写数据public static void writeObject2File() throws IOException {//1获取Singleton对象Singleton singleton Singleton.getSingleton();//创建对象输出流对象ObjectOutputStream objectOutputStream new ObjectOutputStream(new FileOutputStream(D:\\a.txt));// 3.写对象objectOutputStream.writeObject(singleton);// 4.释放资源objectOutputStream.close();}
}
解决方案在 Singleton 类中添加readResolve()方法。
在反序列化时被反射调用如果定义了这个方法就返回这个方法的值如果没有定义则返回新 new 出来的对象。
/*** 双重加锁方式(解决序列化破解单例模式)*/
public class Singleton implements Serializable {//1定义静态成员变量在类加载时创建 声明Singleton类型的变量,使用volatile保证可见性和有序性private static volatile Singleton singleton;//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式让外界获取该对象public static Singleton getSingleton(){// 第一次判断,如果instance不为null,不需要抢占锁,直接返回对象if(singletonnull){synchronized (Singleton.class) {// 第二次判断,抢占到锁以后再次判断if (singleton null) {singleton new Singleton();}}}return singleton;}/*** 下面是为了解决序列化反序列化破解单例模式* 当进行反序列化时会自动调用该方法将该方法的返回值直接返回*/private Object readResolve() {return getSingleton();}}反射破坏单例
public class Client {public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {//1 获取Singleton的字节码对象ClassSingleton singletonClass Singleton.class;//2获取无参构造方法对象ConstructorSingleton declaredConstructor singletonClass.getDeclaredConstructor();//3取消访问检查declaredConstructor.setAccessible(true);// 4.创建Singleton对象Singleton s1 (Singleton) declaredConstructor.newInstance();Singleton s2 (Singleton) declaredConstructor.newInstance();System.out.println(s1 s2); // false}
}解决方案当通过反射方式调用构造方法进行创建时直接抛异常
public class Singleton {//1定义静态成员变量在类加载时创建 声明Singleton类型的变量,使用volatile保证可见性和有序性private static volatile Singleton singleton;private static boolean flag false;//2私有化构造方法// 私有构造方法private Singleton() {synchronized (Singleton.class) {// 如果是true,说明非第一次访问,直接抛一个异常,如果是false,说明第一次访问if (flag) {throw new RuntimeException(不能创建多个对象);}flag true;}}// 3.提供一个公共的访问方式让外界获取该对象public static Singleton getSingleton(){// 第一次判断,如果instance不为null,不需要抢占锁,直接返回对象if(singletonnull){synchronized (Singleton.class) {// 第二次判断,抢占到锁以后再次判断if (singleton null) {singleton new Singleton();}}}return singleton;}
}
JDK 源码 - Runtime 类
Runtime 类使用的就是单例设计模式。
源码查看下面是 Runtime 类的源码是静态变量方式的饿汉单例模式。
public class Runtime {private static Runtime currentRuntime new Runtime();/*** Returns the runtime object associated with the current Java application.* Most of the methods of class codeRuntime/code are instance* methods and must be invoked with respect to the current runtime object.** return the codeRuntime/code object associated with the current* Java application.*/public static Runtime getRuntime() {return currentRuntime;}/** Dont let anyone else instantiate this class */private Runtime() {}...
}
/*** RuntimeDemo*/
public class RuntimeDemo {public static void main(String[] args) throws IOException {// 获取Runtime类的对象Runtime runtime Runtime.getRuntime();// 调用runtime的方法exec,参数要的是一个命令Process process runtime.exec(ifconfig);// 调用process对象的获取输入流的方法InputStream is process.getInputStream();byte[] arr new byte[1024 * 1024 * 100];// 读取数据int len is.read(arr); // 返回读到的字节的个数// 将字节数组转换为字符串输出到控制台System.out.println(new String(arr, 0, len, GBK));}
}