一定要掌握的几种设计模式

本文详细介绍了Java中的单例模式,包括饿汉式、懒汉式、双重校验、静态内部类和枚举实现方式。重点讨论了线程安全和性能优化,并总结了各种情况下的推荐使用方式。单例模式常用于控制资源、确保只有一个实例的情况,如日志文件、应用配置、线程池等。
摘要由CSDN通过智能技术生成

1.单例模式

一、单例模式
二、总结

一、单例模式(Singleton)

1.什么是单例模式?
·单例类只能 有一个实例。
·单例类必须自己创建自己的唯一实例。
·单例类必须给所有其他对象提供 这一实例。

2.为什么会有单例模式?
·当您想控制实例数目,节省系统资源的时候会需要用到单例模式。

应用场景:
①Windows的任务管理器。一个Windows只能打开一个任务管理器;
②网站的计数器,一般也采用单例模式,否则难以同步。
③多线程的线程池一般也采用单例模式,这是由于线程池需要对池中的线 程进行管理

3.怎么使用单例模式?如何创建?(6种方式)
·饿汉式
①线程是安全的。—并不是加锁,且执行效率高。
因为类加载到内存后,就实例化一个单例,JVM来保证线程安全。(JVM保证每一个类被加载一次,既然加载内存是一次的话,那类是静态的情况下,加载到内存之后就马上开始初始化了。所以也保证初始化也是一次,故是线程安全的。)
②浪费内存资源。由于不管类用到与否,类加载的时候就完成了初始化。没有达到lazy loading的效果。

public class SingletonDemo {

    private static final SingletonDemo INSTANCE = new SingletonDemo();//1.先创建出实例

    private SingletonDemo() {};//2.构造方法设置成private,别人无法new出实例

    public static SingletonDemo getInstance() {return INSTANCE;}//3.获取实例

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        SingletonDemo m1 = SingletonDemo.getInstance();
        SingletonDemo m2 = SingletonDemo.getInstance();
        System.out.println(m1==m2);//测试得到的实例都是同一个实例。
    }
}

·懒汉式
虽然满足了按需初始化的目的,但是却带来了线程不安全。所以不支持多线程的环境,因为没有加锁。严格意义上来讲不算单例模式。

public class LazyLoadingDemo {
    private static LazyLoadingDemo INSTANCE;//1.最开始没有做初始化操作;

    private LazyLoadingDemo() {//2.构造方法设置为private,这样就new不出来;

    }

    public static LazyLoadingDemo getInstance() {//3.获取实例
        if (INSTANCE== null) {
            try {
                Thread.sleep(1);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
            INSTANCE = new LazyLoadingDemo();//第一次调用getInstance方法之后如果判断是空,就初始化。
        }                                    //第二次再判断的时候已经发现INSTANCE不是空的了,则返回原来的INSTANCE。
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }


    /*
    * 此处利用多线程来测试线程不安全。
    * */
    public static void main(String[] args) {
        for (int i = 0; i<100; i++) {
            new Thread(()->{
                System.out.println(LazyLoadingDemo.getInstance().hashCode());//不同对象的哈希码是不同的。
            }).start();
        }
    }
}

以下是懒汉式的前提下,满足线程的安全。—加synchronized关键字。
虽然可以支持多线程了,但是执行效率却降低了。

public class LazyLoadingDemo2 {
    private static LazyLoadingDemo2 INSTANCE;//1.最开始没有做初始化操作;

    private LazyLoadingDemo2() {//2.构造方法设置为private,这样就new不出来;

    }

    public static synchronized LazyLoadingDemo2 getInstance() {//3.添加synchronized关键字。由于每次线程会看有没有申请到锁,然后才进行操作,所以就会造成效率降低,但保证了线程安全。
        if (INSTANCE== null) {
            try {
                Thread.sleep(1);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
            INSTANCE = new LazyLoadingDemo2();//第一次调用getInstance方法之后如果判断是空,就初始化。
        }                                    //第二次再判断的时候已经发现INSTANCE不是空的了,则返回原来的INSTANCE。
        return INSTANCE;
    }
 }

·双重校验
①满足lazy loading
②支持多线程,保证线程安全。
③在多线程的情况下,保持高性能。

public class LazyLoadingDemo3 {

    private static volatile LazyLoadingDemo3 INSTANCE;//1.最开始没有做初始化操作;需要加上volatile,防止重排(如果做了JIT优化的话)。

    private LazyLoadingDemo3() {//2.构造方法设置为private,这样就new不出来;

    }

    public static LazyLoadingDemo3 getInstance() {//3.获取实例
        if (INSTANCE== null) {//①第一遍检查,先判断INSTANCE是否为空,如果是空就上锁。
            //双重检查
            synchronized (LazyLoadingDemo3.class) {//②第二遍检查
                if (INSTANCE == null) {
                    try {
                        Thread.sleep(1);
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new LazyLoadingDemo3();//第一次调用getInstance方法之后如果判断是空,就初始化。
                }                                    //第二次再判断的时候已经发现INSTANCE不是空的了,则返回原来的INSTANCE。

                }
            }
        return INSTANCE;
    }
}

·静态内部类
这种方式能达到双重校验方式一样的效果,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双重校验方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

原因:static可以实现类只加载一次,加载外部类的时候不会加载内部类,所以实现了lazy loading。

public class LazyLoadingDemo4 {//在加载的时候不会加载静态内部类,只有在调用内部类的时候才会去加载。

    private LazyLoadingDemo4() {
    }

    private static class LdHolder {//1.在LazyLoadingDemo4中定义了一个静态内部类,然后在静态内部类中初始化了一个LazyLoadingDemo4。因为LazyLoadingDemo4的构造方法是private的。所以只有它的内部类才能new的出来。
        private final static LazyLoadingDemo4 INSTANCE = new LazyLoadingDemo4();//2.在静态内部类中初始化了一个LazyLoadingDemo4
    }

    public  static LazyLoadingDemo4 getInstance() {
        return LdHolder.INSTANCE;//3.返回的是静态内部类里面的外部类的INSTANCE
    }

    public  void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        for (int i =0; i<100; i++) {
            new Thread(()->{
                System.out.println(LazyLoadingDemo4.getInstance().hashCode());
            }).start();
        }
    }

·枚举
它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。

public enum Singleton {  
    INSTANCE;  //1.直接先写好实例
    public void whateverMethod() {  
    }  
}

二、总结
1.单例模式应用的场景一般发现在以下条件下:

(1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。

(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。
  
2.各类情况的推荐使用方式如下:
(1)一般情况下,不建议使用懒汉方式,建议使用饿汉方式。
(2)只有在要明确实现 lazy loading 效果时,才会使用第静态内部方式。
(3)如果涉及到反序列化创建对象时,可以尝试使用枚举方式。
(4)如果有其他特殊的需求,可以考虑使用双重校验方式。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值