单例设计模式(Singleton Pattern)是Java开发人员了解设计模式的第一种,也是最容易理解的,在平时的工作使用的很频繁的设计模式之一!
概念
单例设计模式(Singleton Pattern) :确保每一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法,属于创建型模式。
-
定义:
- 私有化该类的构造函数
- 通过new在本类中创建一个本类对象
- 定义一个公有的方法,将在该类中所创建的对象返回
-
确保对象的唯一性:
- 不允许其他程序用new对象
- 在该类中创建对象
- 对外提供一个可以让其他程序获取该对象的方法
单例实现:在单例类的内部实现只生成一个实例,同时它提供一个静态的
getInstance()
工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为**私有 ;在单例类内部定义了一个Singleton
类型的静态 **对象,作为外部共享的唯一实例。
作用
单例设计模式主要是为了避免因为创建多个实例造成的资源浪费,且多个实例由于多次调用容易导致结果出现错误,而使用单例设计模式能够保证整个应用中有且只有一个实例。
实现方式
饿汉式
优点:在类加载 的时候就完成了实例化,避免了多线程同步问题。
缺点:由于在类加载的时候就实例化了,所以没有达到Lazy Loading
(懒加载)的效果,也就是说我没有用到这个实例,但是它也会加载,会造成内存的浪费(但是这个浪费可以忽略的)
publice class Singleton{
//在类加载的时候就创建一个对象
private static Singleton instance = new Singleton();
//初始化构造器
private Singleton(){}
//获取单例的对象
public static Singleton getInstance(){
return instance;
}
}
懒汉式
基础版
优点:
缺点:当多线程工作的时候,如果有多个线程同时运行到if(instance == null)
都判断为空,那么这两个线程各自都会创建一个实例,这样就不是单例了。
public class Singleton{
//先定义对象的引用
private static Singleton instance;
//用作初始化的构造器,定义为私有,防止外部的类调用
private static Singleton(){};
public static Singleton getInstance(){
//如果多个线程在此处,则每个线程都会创建一个实例
if(instance == null){
instance =new Singleton();
}
return instance;
}
}
Synchronized版本
优点:加上了synchronized
关键字后,getInstance()
方法就会锁上了。如果有两个线程同时执行到这个方法是,会有其中一个线程获得同步锁,进而继续执行,而另外一个线程需要等待,当T1线程执行完毕之后,T2才会执行。
缺点:加上了同步锁之后,会强制除T1以外的所有线程等待,会严重影响程序的执行效率
。
双重检查版本(Double-Check)
优点:在同步锁的外面在做一次判断,如果这个线程的实例没有被创建过,则放入;如果被创建过,则直接返回它的实例。
缺点:使用volatile
关键字会屏蔽Java虚拟机所做的一些代码优化,可能导致系统运行效率低。
注意:如果使用双重检查锁来实现懒汉式单例类,需要在静态成员变量实例之前增加修饰符volatile
,被volatile
修饰的成员变量可以确保多个线程都能够正确处理。
public class Singleton{
//添加volatile 关键字
private static volatile Singleton instance;
private static Singleton();
private static Singleton getInstance(){
//第一次判断:该线程的实例是否被创建
if(instance == null){
//使用同步代码块,由于此方法是静态的,所以同步锁是类名.class
synchronized(Singleton.class){
//第二次判断:为了防止了可能出现的多个实例的情况
if(instance == null){
intstance = new Singleton();
}
}
}
return instance;
}
}
静态内部类
优点:饿汉式单例类不是实现延时加载,不管将来用不用始终占据内存;懒汉式单例类线程安全控制繁琐,而且性能受影响。我们在单例类中增加一个**静态内部类 **,在该内部类中创建单例对象,再将该单例对象通过getinstance
方法返回给外部使用。
由于静态单例对象没有作为Singleton
的成员变量直接实例化,因此类加载时不会实例化Singleton
,第一次调用getInstance()
时将加载内部类SingletonHolder
,在该内部类中定义了一个static
类型的变量instance
,此时会首先初始化这个变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。
缺点:与编程语言本身的特性相关,很多面向对象语言不支持IoDH
。
public class Singleton{
//静态内部类
private static class SingletonHolder{
private static final Singleton instance = new Singleton();
}
private Singleton(){};
//内部类是在需要被实例化时,调用getInstance()方法时,才会去装载SingletonHolder类
public static final Singleton getInstance(){
return SingletonHolder.instance;
}
}
枚举
优点:它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次序列化。
缺点:不能通过reflection attack
来调用构造方法。
public enum SingletonEnum{
//枚举类型变量
instance;
public SingletonEnum(){
}
public void whateverMethod(){
}
}
Java中的单例模式
Java的Runtime对象
在Java语言内部,java.lang.Runtime
对象就是一个使用单例模式的例子。在每一个Java程序里面,都是唯一的一个Runtime对象,应用程序可以与其运行环境发生相互作用。
Runtime
类提供了一个静态工厂方法getRuntime()
:
public static Runtime getRuntime();
通过调用此方法,可以获得Runtime
类唯一的一个实例:
Runtime rt = Runtime.getInstance();
单例模式的优缺点
优点:
- 单例模式提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及如何访问它。
- 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
- 允许可变数目的实例。基于单例模式我们可以进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例,既节省系统资源,又解决了单例对象共享过多有损性能的问题。
缺点:
- 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
- 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
- 现在很多面向对象语言的运行环境都提供了自动垃圾回收的技术,因此,如果实例化对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致共享的单例对象状态的丢失。
应用场景
在一下情况下可以考虑使用单例模式:
- 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。
- 客户调用类的单例实例只允许使用一个公共访问节点,除了该公共访问点,不能通过其他路径访问该实例。