单例模式和实现详解

目录

简介

单例模式实现

1、懒汉式,线程不安全

2、懒汉式,线程安全

3、饿汉式

4、双检锁/双重校验锁

5、静态内部类

6、枚举实现


简介

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

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

假如一个对象被频繁的使用和销毁,那么对于内存来说无疑是比较浪费的,就比如说我们操作数据库,我们创建数据库连接去操作数据库,我们创建的数据库连接明明是可以重复利用的,但是我们却不停的创建和销毁,这就造成了资源的浪费,不光如此,如果我们频繁的创建连接去操作数据库,这也是非常危险的。这时就需要单例模式了,创造一个对象,并且重复利用,节省CPU了资源。

  • 单例类只能有一个实例。
  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其他对象提供这一实例。

 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决:一个全局使用的类频繁地创建与销毁。

何时使用:当您想控制实例数目,节省系统资源的时候。

如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:构造函数是私有的。

应用场景:

  • 对于 Java 来说,单例模式可以保证在一个 JVM 中只存在单一实例。单例模式的应用场景主要有以下几个方面。
  • 需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少 GC。
  • 某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
  • 某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用。
  • 某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
  • 频繁访问数据库或文件的对象。
  • 对于一些控制硬件级别的操作,或者从系统上来讲应当是单一控制逻辑的操作,如果有多个实例,则系统会完全乱套。
  • 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。

单例模式实现

通常,普通类的构造函数是公有的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。

要实现单例模式,需要考虑以下三点:

  • 是否线程安全
  • 是不是懒加载
  • 能不能通过反射破坏

1、懒汉式,线程不安全

懒加载

线程不安全:在执行if(instance==null)时可能会有多个,可能有多个不同的线程同时进入,实例化多次

public class Singleton {  
    private static Singleton instance=null;    //初始化对象为null  
    private Singleton (){}  //构造器私有
  
    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

2、懒汉式,线程安全

懒加载

将代码改造成这样就实现了线程安全。但其实我们只想要在创建对象时进行同步操作,但是现在每次获取对象都需要进行同步操作了,对象能影响非常大,这种写法大部分情况下不可取。

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

3、饿汉式

不是懒加载

线程安全

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}

4、双检锁/双重校验锁

懒加载

线程安全

假设a、b两个线程同时进入getInstance方法,实现a获取了锁并进行了instance构建,完成后交还锁,此时b进入锁,此时已经完成instance,所以b不会再创建一个新的instance,保证了线程安全。这种对象进行两次判空的操作叫做双检锁

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;  
    }  
}

问题:

由于singleton=new Singleton在指令层面不是一个原子操作,它分为了三步(分配内存、初始化对象、对象指向内存地址),在执行时虚拟机为了效率可能会进行指令重排,比如说先执行对象第一步、第三步,在执行第二步,在这种情况是执行到了三步时,instance还未被初始化,假如在此时线程b执行到了 instance==null 这一步,返回false直接跳过,但是此时a线程内的instance还未被初始化,所以灾难发生了,b线程调用getInstance返回了未初始化,出现了线程不安全的情况。所以给加上volatile(而不是static)阻止指令重排序

缺点:写起来比较麻烦

 Java中的volatile变量是什么?

(1)关键字的作用有两个:

          ①多线程主要围绕可见性和原子性两个特性展开,使用volatile关键字修饰的变量,保证了其在多线程之间的可见性,即每次读取到的volatile变量,一定是最新的数据。

         ②代码底层执行的顺序是Java代码-->字节码-->根据字节码执行对应的C/C++代码-->C/C++代码被编译成汇编语言-->和硬件电路交互。实际中,为了获取更好的性能,JVM可能会对指令进行重排序,多线程下可能会出现一些意想不到的问题。使用volatile则会禁止语义重排序,也一定程度上降低了代码执行效率。实践角度而言,volatile的一个重要作用就是和CAS结合,保证了原子性。

(2)volatile是一个特殊的修饰符,只有成员变量才能使用它。在Java并发程序缺少同步类的情况下,多线程对成员变量的操作对其他线程是透明的。volatile变量可以保证下一个读取操作会在前一个写操作之后发生。

5、静态内部类

利用JVM类加载机制,静态内部类的特点就是在类加载阶段不会执行,只有类被第一次调用的时候静态内部类才会被加载,这就很好的符合了我们的单例要求。

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、枚举实现

无法通过反射破坏

public enum Singleton {
    INSTANCE;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

过街的老鼠

感谢你对诗仙女的打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值