三、单例模式
单例模式(Singleton Pattern)是Java中最简单的设计模式之一,涉及到一个单一的类,负责创建自己的对象且确保唯一,并提供对外调用方式。
- 单例类只能有一个实例对象
- 单例类必须自己创建自己的唯一实例对象
- 单例类必须给所有其他对象提供这一实例。
介绍
单例类提供一个可供全局访问的唯一的实例对象,用于全局频繁操作的单一对象,有助于节省系统资源。单例模式构造函数私有。
应用场景举例:1、多线程文件操作时候,操作文件的对象需要单一,以保证操作安全(当然需要配合线程同步)。2、唯一序列号的产生。3、web网站全局计数器。
- 优点:1、内存里只有一个实例,减少内存开销,尤其是频繁的创建与销毁实例。2、避免对资源的多重占用(如操作文件)。
- 缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不应关心外部实例化的问题。
**注意事项:**getInstance()方法中需要使用同步锁synchronized(Singleton.class)
防止多线程同时进入造成instance多次实例化。
实现
创建一个SingleObject类,私有化其构造函数并创建一个静态实例,对外提供一个静态方法,来获取它的静态实例。SingletonPatternDemo,来演示获取SingleObject的单例对象。
创建Singleton类
SingleObject.java
public class SingleObject {
//创建一个SingleObject的对象
private static SingleObject instance = new SingleObject();
//私有化构造函数
private SingleObject(){}
//对外提供对象实例
public static SingleObject getInstance(){
return instance;
}
//单例类的其他方法
public void showMsg(){
System.out.println("Hello World!");
}
}
获单一实例
SingletonPatternDemo.java
public class SinglePatternDemo {
public static void main(String[] args){
//单例模式对象实例的获取,不能用new的方式,因为其构造函数私有化了,只能通过它提供的实例获取方法获取实例对象。
SingleObject object = SingleObject.getInstance();
//调用对象的其他方法
object.showMsg();
}
}
- 输出结果
Hello World!
单例模式的集中实现方式
单例模式有多种实现模式
懒汉式、线程不安全
lazy loading、非线程安全、简单易实现;由于非线程安全,严格意义上不算单例模式。但是在不要求线程安全的情况下,简便易用。
public class Singleton {
private static Singleton instance;
private Singleton(){}
//对外提供对象单例
public static Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
以下都是线程安全的单例模式,效率不同
懒汉式、线程安全
线程安全形式的lazy loading,支持多线程,简单,但是效率低。
- 优点:第一次调用才初始化,避免内存浪费。
- 缺点:必须加
synchronized
锁才能保证单例,而影响效率。
public class Singleton {
private static Singleton instance;
private Singleton(){}
//同步锁方法,对外提供单例对象
public static synchronized Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
饿汉式
饿汉式简单常用,线程安全,但是易产生垃圾。
- 优点:无加锁,效率高。
- 缺点:静态初始化,浪费内存。
饿汉式通过
classloader
机制来避免多线程同步问题。但并没有达到lazy loading的记载效果。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){}
//类似懒汉式,但是静态初始化对象实例了。
public static Singleton getInstance(){
ruturn instance;
}
}
双检锁/双重校验锁(DCL,即double-checked locking)
采用双锁机制,多线程下高效执行,但是实现难度大。lazy loading、线程安全、复杂。且在JDK1.5后采用。
public class Singleton {
private volatile static Singleton singleton;
private Singleton(){}
//
public static Singleton getInstance(){
if (singleton == null){
//同步代码块
synchronized (Singleton.class){
//再次判断是否非空,避免多线程风险。
if (singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
登记式/静态内部类
lazy loading、线程安全、难度一般。该形式可以达到如上双检锁一样的功效,实现更方便。仅适用于静态域延迟初始化,而双检锁方式可在实例域需要延迟初始化时使用。
登记式同样利用了classloader机制,不同于饿汉式:1、饿汉式的instance随Singleton
类初始化,没有lazy loading(lazy可避免浪费内存);而登记式的instance不一定随Singleton初始化,因为Singleton Holder类没有被主动使用。所以登记式比饿汉式更合理。
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton(){}
//登记式对外单例
public static final Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
枚举
不lazy loading、线程安全、简单。这是实现单例模式最佳的方式,但开发中使用者较少。简洁、自动序列化、绝对防止多次实例化。JDK1.5以后采用。
public enum Singleton {
INSTANCE;
public void whateverMethod(){
}
}
Note:一般不建议使用第1、2种懒汉方式,建议使用第3中饿汉式。只有明确实现lazy loading效果时才使用第5种登记式。涉及到反序列化对象时候,尝试第6种枚举式。若有特殊要求,可以考虑第4中双检锁方式。