设计一个单例模式的类,java单例模式应用场景举例

  设计一个单例模式的类,java单例模式应用场景举例

  00-1010单一模式1。单一模式2的结构。实现单例模式2.1饿汉2.2懒汉3。破坏单例模式3.1序列化和反序列化3.2反射

  00-1010 singleton模式,顾名思义,就是单个实例,涉及单个类,负责创建自己的对象,保证只创建一个对象,并提供一种直接访问这个对象的方法,不需要实例化这个类的对象。

  单例模式的特征:

  1.单例类只能有一个实例。

  2.这个实例必须由singleton类自己创建。

  3.需要向外界提供singleton类来访问这个实例。

  单例模式的作用:

  Singleton模式主要是保证Java应用程序中一个类只有一个实例。

  00-1010单例模式主要有以下作用:

  单例类只能创建一个有一个实例的类。

  类访问测试类是一个使用单例类的类。

  

目录

 

  00-1010 Hungry-Chinese:在加载类时创建这个单实例类对象。

  1.饥饿的中国式-模式1静态成员变量

  创建一个饥饿静态成员变量的单例类。

  public demo1 {/* * * private构造方法阻止外界创建这个类对象*/private demo1 () {}/* *这个类对象的static是在类中创建的,因为外界获取这个类对象的方法getInstance()是static *这个对象实例是一个静态成员变量*/private static demo 1 instance=new demo 1();/* * *提供了一个公共访问方法,让外界可以得到这个类的对象静态,因为外界不需要创建对象,直接访问*/public static demo 1 getinstance(){ return instance;}}创建一个饿中文静态成员变量测试类(访问类)

  public test1 { public static void main(string[]args){//类demo1的对象此时不能由new创建,因为demo1的构造方法是private demo 1 instance=demo 1 . getinstance();demo 1 instance 1=demo 1 . getinstance();//判断两个对象是否为同一个system . out . println(instance==instance 1);}} Output true表示是同一个对象,指向同一个内存地址,这样我们就可以保证只创建一个Demo1 singleton类的对象。

  2.饥饿中国式-模式2静态代码块

  创建饥饿中文静态代码块的单例类

  class demo 2 {//Hungry-Chinese singleton静态代码块/* *私有构造方法阻止外界创建此类对象*/PrivateDemo2 () {}/* *声明一个静态成员变量实例但不赋值(不创建对象)*不为实例赋值,默认值为null */Private static demo 2 instance;/* * *为静态代码缓存中的实例赋值(创建对象*/static { instance=new demo 2();}/* * *提供了一个公共的访问方法,让外界可以得到这个类的对象静态,因为外界不需要创建对象,直接访问*/public static demo 2 getinstance(){ return instance;}}创建一个饥渴的中文静态代码块测试类

  public class Test2 { public static void main(String[]args){ demo 2 instance=demo 2 . getinstance();demo 2 instance 1=demo 2 . getinstance();system . out . println(instance==instance 1);}}} Output true表示是同一个对象,指向同一个内存地址,这样我们保证Demo2单例。

  类只有一个对象被创建

  3.饿汉式-方式3(枚举方式)

  枚举类实现单例模式是十分推荐的一种单例实现模式,由于枚举类型是线程安全的,并且只会加载一次,这是十分符合单例模式的特点的,枚举的写法很简单,而且枚举方式是所有单例实现中唯一一个不会被破环的单例实现模式

  单例类

  

//枚举方式创建单例public enum Singleton { INSTANCE;}

测试类

 

  

public class Test1 { public static void main(String[] args) { Singleton instance = Singleton.INSTANCE; Singleton instance1 = Singleton.INSTANCE; System.out.println(instance == instance1); //输出 true }}

注意:

 

   由于枚举方式是饿汉式,因此根据饿汉式的特点,枚举方式也会造成内存浪费,但是在不考虑内存问题下,枚举方式是首选,毕竟实现最简单了

  

 

  

2.2懒汉式

懒汉式:类加载时不会创建该单实例对象,首次使用该对象时才会创建

 

  1.懒汉式-方式1 (线程不安全)

  

public class Demo3 { /** *私有构造方法 让外界不能创建该类对象 */ private Demo3(){} /** * 在类中创建该本类对象 static是由于外界获取该类对象的方法getInstance()是 static * 没有进行赋值(创建对象) */ private static Demo3 instance; /** * 提供一个公共的访问方式,让外界可以获取该类的对象 static是因为外界不需要创建对象,直接通过类访问 */ public static Demo3 getInstance(){ //在首次使用该对象时创建,因此instance赋值也就是对象创建 就是在外界获取该单例类的方法getInstance()中创建 instance = new Demo3(); return instance; }}
public class Test3 { public static void main(String[] args) { Demo3 instance = Demo3.getInstance(); Demo3 instance1 = Demo3.getInstance(); //判断两个对象是否是同一个 System.out.println(instance == instance1); }}

输出结果为false,表明我们创建懒汉式单例失败了。是因为我们在调用getInstance()时每次调用都会new一个实例对象,那么也就必然不可能相等了。

 

  

 // 如果instance为null,表明还没有创建该类的对象,那么就进行创建 if(instance == null){ instance = new Demo3(); } //如果instance不为null,表明已经创建过该类的对象,根据单例类只能创建一个对象的特点,因此 //我们直接返回instance return instance; }

注意:

 

  我们在测试是只是单线程,但是在实际应用中必须要考虑到多线程的问题。我们假设一种情况,线程1进入if判断然后还没来得及创建instance,这个时候线程1失去了cpu的执行权变为阻塞状态,线程2获取cpu执行权,然后进行if判断此时instance还是null,因此线程2为instance赋值创建了该单例对象,那么等到线程1再次获取cpu执行权,也进行了instance赋值创建了该单例对象,单例模式被破坏。

  2.懒汉式-方式2 (线程安全)

  我们可以通过加synchronized同步锁的方式保证单例模式在多线程下依旧有效

  

 public static synchronized Demo3 getInstance(){ //在首次使用该对象时创建,因此instance赋值也就是对象创建 就是在外界获取该单例类的方法getInstance()中创建 // 如果instance为null,表明还没有创建该类的对象,那么就进行创建 if(instance == null){ instance = new Demo3(); } //如果instance不为null,表明已经创建过该类的对象,根据单例类只能创建一个对象的特点,因此我们直接返回instance return instance; }

注意:

 

  虽然保证了线程安全问题,但是在getInstance()方法上添加了synchronized关键字,导致该方法执行效率很低(这是加锁的一个常见问题)。其实我们可以很容易发现,我们只是在判断instance时需要解决多线程的安全问题,而没必要在getInstance()上加锁

  3.懒汉式-方式3(双重检查锁)

  对于getInstance()方法来说,绝大部分的操作都是读操作,读操作是线程安全的,没必要让每个线程必须持有锁才能调用该方法,我们可以调整加锁的时机。

  

public class Demo4 { /** *私有构造方法 让外界不能创建该类对象 */ private Demo4(){} /** * * 没有进行赋值(创建对象) 只是声明了一个该类的变量 */ private static Demo4 instance; /** * 提供一个公共的访问方式,让外界可以获取该类的对象 static是因为外界不需要创建对象,直接通过类访问 */ public static Demo4 getInstance(){ // (第一次判断)如果instance为null,表明还没有创建该类的对象,那么就进行创建 if(instance == null){ synchronized (Demo4.class){ //第二次判断 如果instance不为null if(instance == null){ instance = new Demo4(); } } } //如果instance不为null,表明已经创建过该单例类的对象,不需要抢占锁,直接返回 return instance; }}

双重检查锁模式完美的解决了单例、性能、线程安全问题,但是只是这样还是有问题的…

 

  JVM在创建对象时会进行优化和指令重排,在多线程下可能会发生空指针异常的问题,可以使用volatile关键字,volatile可以保证可见性和有序性。

  

 private static volatile Demo4 instance;

 

  如果发生指令重排 2 和 3 的步骤颠倒,那么instance会指向一块虚无的内存(也有可能是有数据的一块内存)

  完整代码

  

public class Demo4 { /** *私有构造方法 让外界不能创建该类对象 */ private Demo4(){} /** * volatile可以保证有序性 * 没有进行赋值(创建对象) 只是声明了一个该类的变量 */ private static volatile Demo4 instance; /** * 提供一个公共的访问方式,让外界可以获取该类的对象 static是因为外界不需要创建对象,直接通过类访问 */ public static Demo4 getInstance(){ // (第一次判断)如果instance为null,表明还没有创建该类的对象,那么就进行创建 if(instance == null){ synchronized (Demo4.class){ //第二次判断 如果instance不为null if(instance == null){ instance = new Demo4(); } } } //如果instance不为null,表明已经创建过该单例类的对象,不需要抢占锁,直接返回 return instance; }}

4.懒汉式-4 (静态内部类)

 

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

  创建单例类

  

public class Singleton { private Singleton(){} /** *定义一个静态内部类 */ private static class SingletonHolder{ //在静态内部类中创建外部类的对象 private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ return SingletonHolder.INSTANCE; }}

创建测试类

 

  

public class Test4 { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance1 = Singleton.getInstance(); //判断两个对象是否是同一个 System.out.println(instance == instance1); }}

注意:

 

   第一次加载Singleton类时不会去初始化INSTANCE,只有在调用getInstance()方法时,JVM加载SingletonHolder并初始化INSTANCE,这样可以保证线程安全,并且Singleton类的唯一性

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

  

 

  

3.单例模式的破坏

单例模式最重要的一个特点就是只能创建一个实例对象,那么如果能使单例类能创建多个就破坏了单例模式(除了枚举方式)破坏单例模式的方式有两种:

 

  

 

  

3.1序列化和反序列化

从以上创建单例模式的方式中任选一种(除枚举方式),例如静态内部类方式

 

  

//记得要实现Serializable序列化接口public class Singleton implements Serializable { private Singleton(){} /** *定义一个静态内部类 */ private static class SingletonHolder{ //在静态内部类中创建外部类的对象 private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ return SingletonHolder.INSTANCE; }}

测试类

 

  

public class Test1 { public static void main(String[] args) throws IOException { writeObjectToFile(); } /** * 向文件中写数据(对象) * @throws IOException */ public static void writeObjectToFile() throws IOException { //1.获取singleton对象 Singleton instance = Singleton.getInstance(); //2.创建对象输出流对象 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:\1.txt")); //3.写对象 oos.writeObject(instance); //4.释放资源 oos.close(); }}

在d盘根目录下出现一个文件1.txt由于数据是序列化后的 咱也看不懂

 

  然后我们从这个文件中读取instance对象

  

public static void main(String[] args) throws Exception { // writeObjectToFile(); readObjectFromFile(); readObjectFromFile(); } /** * 从文件中读数据(对象) * @throws Exception */ public static void readObjectFromFile() throws Exception { //1.创建对象输入流对象 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:\1.txt")); //2.读对象 Singleton instance = (Singleton) ois.readObject(); System.out.println(instance); //3.释放资源 ois.close(); }

输出结果不相同,结论为:序列化破坏了单例模式,两次读的对象不一样了

 

  

com.xue.demo01.Singleton@2328c243com.xue.demo01.Singleton@bebdb06

 

  

解决方案

 

  在singleton中添加readResolve方法

  

 /** * 当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回 * @return */ public Object readResolve(){ return SingletonHolder.INSTANCE; }

重新进行写和读,发现两次读的结果是相同的,解决了序列化破坏单例模式的问题

 

  为什么在singleton单例类中添加readResolve方法就可以解决序列化破坏单例的问题呢,我们在ObjectInputStream源码中在readOrdinaryObject方法中

  

 private Object readOrdinaryObject(boolean unshared) throws IOException{//代码段 Object obj; try { //isInstantiable如果一个实现序列化的类在运行时被实例化就返回true //desc.newInstance()会通过反射调用无参构造创建一个新的对象 obj = desc.isInstantiable() ? desc.newInstance() : null; } catch (Exception ex) { throw (IOException) new InvalidClassException( desc.forClass().getName(), "unable to create instance").initCause(ex); } //代码段 if (obj != null && handles.lookupException(passHandle) == null && //hasReadResolveMethod 如果实现序列化接口的类中定义了readResolve方法就返回true desc.hasReadResolveMethod()) { //通过反射的方式调用被反序列化类的readResolve方法 Object rep = desc.invokeReadResolve(obj); if (unshared && rep.getClass().isArray()) { rep = cloneArray(rep); } //代码段 }

 

  

3.2反射

从以上创建单例模式的方式中任选一种(除枚举方式),例如静态内部类方式

 

  测试类

  

public class Test1 { public static void main(String[] args) throws Exception { //1.获取Singleton的字节码对象 Class<Singleton> singletonClass = Singleton.class; //2.获取无参构造方法对象 Constructor cons = singletonClass.getDeclaredConstructor(); //3.取消访问检查 cons.setAccessible(true); //4.反射创建对象 Singleton instance1 = (Singleton) cons.newInstance(); Singleton instance2 = (Singleton) cons.newInstance(); System.out.println(instance1 == instance2); //输出false 说明反射破坏了单例模式 }}

解决方案:

 

  

public class Singleton { //static是为了都能访问 private static boolean flag = false; private Singleton() { //加上同步锁,防止多线程并发问题 synchronized (Singleton.class) { //判断flag是否为true,如果为true说明不是第一次创建,抛异常 if (flag) { throw new RuntimeException("不能创建多个对象"); } //flag的值置为true flag = true; } } /** *定义一个静态内部类 */ private static class SingletonHolder{ //在静态内部类中创建外部类的对象 private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ return SingletonHolder.INSTANCE; }}

这样就不能通过之前的反射方式破坏单例模式了,但是如果通过反射修改flag的值也是可以破坏单例模式的,但是这样可以防止意外反射破坏单例模式,如果刻意破坏是很难防范的,毕竟反射太强了

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: