单例模式和反射模式

1.什么是单例模式?

单例模式是一种设计模式,它保证一个类只有一个实例,并提供一个全局访问该实例的方式。在单例模式中,类自身负责创建自己的唯一实例,并且确保任何其他对象都只能访问到这一实例。

单例模式常用于需要全局共享资源或控制某个唯一对象的场景,例如数据库连接池、线程池、日志记录器等。通过使用单例模式,可以避免多个实例的创建和资源的浪费,同时简化了对象的管理和调用。

2.用单例模式有什么好处?

  1. 确保只有一个实例:单例模式可以确保一个类只有一个实例存在,这样可以节省系统资源,避免多个实例引起的性能问题和内存浪费。

  2. 方便全局访问:通过单例模式,可以提供一个全局访问点来获取唯一实例。这样,在需要使用该实例的任何地方,都可以通过统一的方式获取到,方便管理和调用。

  3. 数据共享和通信:在某些情况下,多个对象需要共享同一份数据,或者需要进行跨对象的通信。使用单例模式可以确保这些对象都引用同一个实例,从而实现数据共享和通信的需求。

  4. 避免重复创建对象:某些资源较为昂贵或耗时的对象,如果反复创建会导致性能下降。通过单例模式,可以避免重复创建对象,提高系统性能。

  5. 控制实例数量:有些场景下,需要限制某个类的实例数量,例如线程池、数据库连接池等。使用单例模式可以方便地控制实例数量,确保符合需求的对象数量。

总之,单例模式可以提供全局唯一的实例,简化了对象的管理和调用,并且可以节省系统资源。它在很多情况下都是一种有效的设计模式,但需要注意线程安全性和合理使用,避免滥用导致代码复杂度增加。

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

3.单例模式应用实例:

  • 1、一个班级只有一个班主任。
  • 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
  • 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。

4.单例模式使用场景:

  • 1、要求生产唯一序列号。
  • 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
  • 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

5.单例模式注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。

6.单例模式的几种实现方式

(1)懒汉式,线程不安全

是否 Lazy 初始化:

是否多线程安全:

实现难度:

描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}
(2)懒汉式,线程安全

是否 Lazy 初始化:

是否多线程安全:

实现难度:

描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}
(3)饿汉式

是否 Lazy 初始化:

是否多线程安全:

实现难度:

描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}
(4)双检锁/双重校验锁(DCL,即 double-checked locking)

JDK 版本:JDK1.5 起

是否 Lazy 初始化:

是否多线程安全:

实现难度:较复杂

描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
getInstance() 的性能对应用程序很关键。

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
            if (singleton == null) {  
                singleton = new Singleton();  
            }  
        }  
    }  
    return singleton;  
    }  
}
(5)登记式/静态内部类

是否 Lazy 初始化:

是否多线程安全:

实现难度:一般

描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。

public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE;  
    }  
}
(6)枚举

JDK 版本:JDK1.5 起

是否 Lazy 初始化:

是否多线程安全:

实现难度:

描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
不能通过 reflection attack 来调用私有构造方法。

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

经验之谈:一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。

1.什么是反射模式? 

大家都知道,要让Java程序能够运行,那么就得让Java类要被Java虚拟机加载。Java类如果不被Java虚拟机加载,是不能正常运行的。现在我们运行的所有的程序都是在编译期的时候就已经知道了你所需要的那个类的已经被加载了。

Java的反射机制是在编译并不确定是哪个类被加载了,而是在程序运行的时候才加载、探知、自审。使用在编译期并不知道的类。这样的特点就是反射,体现了Java的动态性。

每一个类都会在编译之后都会产生.class文件,当这个类在第一次使用的时候类加载器(ClassLoader)会加载.class到jvm中,

加载:由类加载器完成,找到对应的字节码,创建一个Class对象

链接:验证类中的字节码,为静态域分配空间

初始化:如果该类有超类,则对其初始化,执行静态初始化器和静态初始化块(编译器将检查类型向下转型是否合法,如果不合法将抛出异常。向下转换类型前,可以使用instanceof判断。)
 

2:怎么使用反射
以下就是简单的方法示例,在测试代码中会详细举例

1:获取类

Class c3 = Class.forName("com.thit.DesignPartterns.创建者模式.反射.User");

2:获取构造函数

Constructor<User> constructorsP1 = c3.getConstructor(int.class, String.class);

3:获取对象

User user = constructorsP.newInstance();

4:获取方法

Method m2 = c3.getDeclaredMethod("add",int.class);

m2.setAccessible(true);//私有需要强制才能执行

m2.invoke(user1,5);//执行方法

5:获取字段

Field field = c3.getDeclaredField("id");

field.setAccessible(true);

field.set(user, 1);

 (1)我们先来创建一个java类

 里面放3个私有的属性1个公有的属性(私有的封装),一个公有无参的方法,一个无参私有的方法,一个私有的有参方法,重写tostring方法

package com.cskt.FanShe;

import lombok.Data;

@Data
public class Patient {
    private Integer id;
    private String name;
    private String word;
    public String address;

    public void Patient(){
        System.out.println("孙淇是烧杯");
    }

    private void PatientSi(){
        System.out.println("郑慧婷是烧杯");
    }

    private int PatientSiSan(int a){
        System.out.println("我的id:"+a);
        return a;
    }

    @Override
    public String toString() {
        return "Patient{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", word='" + word + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}
(2)创建一个测试类

 代码如下:

package com.cskt.FanShe;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class TextPatient {
    public static void main(String[] args) throws Exception {

        //获取类 全路径
        Class c3=Class.forName("com.cskt.FanShe.Patient");
        //获取构造函数
        Constructor<Patient> constructor=c3.getConstructor();
        //获取对象
        Patient patient=constructor.newInstance();
        //获取公有的属性
        patient.address="烧杯";


        //获取私有的属性 括号里面放属性名
        Field name = c3.getDeclaredField("name");
        name.setAccessible(true);//开启私有的权限
        name.set(patient,"张三");

        Field word = c3.getDeclaredField("word");
        word.setAccessible(true);
        word.set(patient,"黄晓宇是女王");

        Field id = c3.getDeclaredField("id");
        id.setAccessible(true);
        id.set(patient,1);
        System.out.println(patient.toString());


        //获得公有的无参方法
        Class c4=Class.forName("com.cskt.FanShe.Patient");
        Constructor<Patient> constructor1=c4.getConstructor();
        Patient patient1=constructor1.newInstance();
        patient1.Patient();


        //获得私有的无参方法
        Method patientSi = c4.getDeclaredMethod("PatientSi");
        patientSi.setAccessible(true);
        patientSi.invoke(patient1);


        //获得私有的有参方法
        Method patientSiSan = c4.getDeclaredMethod("PatientSiSan",int.class);
        patientSiSan.setAccessible(true);
        System.out.println(patientSiSan.invoke(patient1,1));
    }
}

运行结果如下:

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值