单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。
单例模式有三个要点:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
单例模式是结构最简单的设计模式一,在它的核心结构中只包含一个被称为单例类的特殊类。
当多个线程同时调用getInstance()方法时,可能有的线程已经进入了if语句的判断里面,但是还没有实例化对象,这时instance依然为null,因此可能多个线程同时创建了
instance实例导致,线程不安全。注意虽然创建了很多个实例,但是前面的实例因为没有被引用,因此最后还是会被虚拟机回收。
单例模式结构图中只包含一个单例角色:
Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。
单例模式是一种比较常见的设计模式。
单例模式作用:
1.控制资源的使用,通过线程同步来控制资源的并发访问;
2.控制实例产生的数量,达到节约资源的目的。
3.作为通信媒介使用,也就是数据共享,它可以在不建立直接关联的条件下,让多个不相关的两个线程或者进程之间实现通信。
单例模式适用场景
在以下情况下可以考虑使用单例模式:
(1) 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。
(2) 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
单例模式优缺点
主要优点:
A.提供了对唯一实例的受控访问。
B.由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
C.允许可变数目的实例。
主要缺点:
A.由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
B.单例类的职责过重,在一定程度上违背了“单一职责原则”。
C.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
1.懒汉模式(线程不安全)
当多个线程同时调用getInstance()方法时,可能有的线程已经进入了if语句的判断里面,但是还没有实例化对象,这时instance依然为null,因此可能多个线程同时创建了
instance实例导致,线程不安全。注意虽然创建了很多个实例,但是前面的实例因为没有被引用,因此最后还是会被虚拟机回收。
public class Singleton
{
private static Singleton instance;
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.饿汉模式(没有线程安全)
类加载器是一个用来加载类文件的类。Java源代码通过javac编译器编译成类文件。然后JVM来执行类文件中的字节码来执行程序。类加载器负责加载文件
系统、网络或其他来源的类文件。有三种默认使用的类加载器:Bootstrap类加载器、Extension类加载器和System类加载器(或者叫作Application类加载器)。
每种类加载器都有设定好从哪里加载类。
饿汉式是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间。
实例通过使用new()关键字创建或者使用class.forName()反射,但它有可能导致ClassNotFoundException。
类的静态方法被调用
类的静态域被赋值
静态域被访问,而且它不是常量
在顶层类中执行assert语句
public class Singleton
{
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance()
{
return instance;
}
}
//类中的静态块会在整个类加载过程中的初始化阶段执行,而不是在类加载过程中的加载阶段执行。初始化阶段是类加载过程中的最后一个阶段
4.饿汉模式(变种)
public class Singleton
{
private Singleton instance = null;
static
{
instance = new Singleton();
}
private Singleton (){}
public static Singleton getInstance()
{
return this.instance;
}
}
5.静态内部类
这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要Singleton类
被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动
使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另
外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。
这个时候,这种方式相比第三和第四种方式就显得很合理。
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.双重校验锁
若不加volatile关键字的话
但是有些编译器为了性能的原因,可能会将第二步和第三步进行重排序,顺序就成了:
分配内存空间
将对象指向刚分配的内存空间
初始化对象
现在考虑重排序后,两个线程发生了以下调用:
可能会导致第二个线程直接获取到没有初始化的对象(已完成将对象指向刚分配的内存空间)
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;
}
}