目录
单例模式简介
单例模式的特点
单例模式的优缺点
单例模式的实现
·饿汉模式
·懒汉模式
·双重锁
·静态内部类
·枚举方法
性能对比选择模式
单例模式简介
单例模式是一种常用的软件设计模式。单例模式确保某个类只有一个实例,即一个类只有一个对象实例。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为,那么经常就会考虑单例模式。
单例模式的特点
(1)单例类只能有一个实例。
(2)单例类必须自己创建自己的唯一实例。
(3)单例类必须给所有其他对象提供这一实例。
从技术实现角度来看,一是单例模式的类只提供私有的构造函数,二是类定义中含有一个该类的静态私有对象,三是该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。
单例模式的优缺点
优点
(1)良好的实例控制,单例模式保证了所有对象都访问唯一实例。
(2)优秀的灵活性,因为类控制了实例化过程,所以类可以灵活更改实例化过程。
缺点
(1)因为每次对象请求都会检查是否存在实例,频繁操作会有不小的开销。
(2)不能解决删除单个对象问题。
单例模式的实现
单例模式的实现方式有多种,下面介绍常见的几种。
·饿汉模式
public class SingletonDemo {
private static SingletonDemo instance=new SingletonDemo();
private SingletonDemo(){
}
public static SingletonDemo getInstance(){
return instance;
}
}
所谓的饿汉模式就是立即加载方式,如代码所示,在类加载初始化时就创建了静态对象供使用,即完成实例化。避免了线程同步问题,所以线程是安全的。此模式实现简单且实例的访问效率高。
而正是因为类加载时完成了实例化,所以单例显然没有达到Lazy loading(延时加载)的效果,所谓的延时加载就是在需要的时候才加载。那么从始至终未使用这个实例,那么就会造成内存的浪费。
·懒汉模式
懒汉模式也就是懒加载模式,即延时加载模式,达到了Lazy loading(延时加载)的效果。因为考虑到线程的安全性、延时加载和效率高低等问题,下面来看看懒汉模式的三种代码实现方式:
方式一:线程不安全,延时加载,效率高
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
此方式达到了延时加载,效率也比较高,但是只能在单线程下使用。如果在多线程下使用,由于可能存在多个线程进入if代码块,创建多个实例,所以线程是不安全的。多线程下不可用此方式。
方式二:线程安全,延时加载,效率极低
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
此方式虽然使用同步方法加锁,解决了以上方式的线程不安全。但是此方式效率极低,因为每个线程想获得实例的时候都要进行线程同步,无奈的等待和由于是单例,那么其方法只执行过一次即可,所以造成效率低下。
方式三:线程不安全,延时加载,效率较高
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
singleton = new Singleton();
}
}
return singleton;
}
}
此方式使用了同步代码块的方式,先判断是否存在实例,如果不存在再执行同步代码块创建实例。虽然这种方式采用先判断是否存在实例后再同步代码块,使得创建了实例后就不再需要一直同步代码块了,其速度上会比使用同步方法快很多,但还是存在线程安全问题。由于多线程情况下,可能存在多个线程同时进入if代码块再进行同步,同样会创建多个实例,造成线程不安全。鉴于这些情况,人们又想到了解决方法,就是双重锁方式。不急,下面会说到!
·双重锁(懒汉式双重效验锁DCL)
public class SingletonDemo {
private volatile static SingletonDemo instance;
private SingletonDemo(){
System.out.println("Singleton has loaded");
}
public static SingletonDemo getInstance(){
if(instance==null){
synchronized (SingletonDemo.class){
if(instance==null){
instance=new SingletonDemo();
}
}
}
return instance;
}
}
双重锁方式单例也是高级版的懒汉模式,从代码中可以看出,在同步代码块再进行判断实例是否存在,解决了以上的线程不安全问题。所以双重锁方式是线程安全的,延时加载的,效率较高的。由于JVM底层模型原因,偶尔会出问题,不建议使用。
·静态内部类
public class SingletonDemo {
private static class SingletonHolder{
private static SingletonDemo instance=new SingletonDemo();
}
private SingletonDemo(){
System.out.println("Singleton has loaded");
}
public static SingletonDemo getInstance(){
return SingletonHolder.instance;
}
}
静态内部类单例模式跟前面的饿汉模式机制类似,但又有细微的不同。饿汉模式在类装载的时候就完成实例化了,虽然线程安全且效率高,但是没有达到延时加载。而静态内部类方式在装载外类的时候并未完成初始化和实例化,而是调用静态工厂方法getInstance()才会装载内部类,从而实现实例化,因此达到了我们想要的延时加载。类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。所以这是推荐使用的单例模式实现方式。
·枚举方法
public enum Singleton {
INSTANCE;
public void otherMethods() {
}
}
public class Hello {
public static void main(String[] args){
Singleton.INSTANCE.otherMethods();
}
}
它不仅能避免多线程同步问题,调用效率高,而且还能天然(JVM提供)防止反射调用构造方法破解单例模式和防止反序列化重新创建新的对象。(当然其他方式也可防止)
注:
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取的信息以及动态调用对象的方法的功能称为JAVA语言的反射机制。
序列化可以将一个单例的实例对象写到磁盘,然后再读回来,从而有效地获得一个实例对象。即使是构造函数是私有的,反序列化时依然可以。
性能对比选择模式
如果需要懒加载就使用静态内部类方式,如果不需要就使用枚举方式。