每天一种设计模式之单例模式(Java实现)
概述
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
实现单例模式的关键是私有化创建逻辑,在创建的时候判断是否有这个实例存在
示例
以麦当劳为例,麦当劳只有一位老板Boss,麦当劳创建后只能new一个老板实例,不能存在第二个老板
在用到老板的时候(比如签字盖章),我们首先要去看有没有老板,如果有就直接让老板签字盖章,没有再去“请”老板
源码实现
简单实现
首先创建一个Boss老板类
public class Boss {
//创建Boss唯一的实例
private static Boss boss = new Boss();
//私有化构造方法,防止外部创建行为
private Boss(){
}
//定义唯一获取实例的方法
public static Boss getInstance(){
return boss;
}
//定义一个测试方法sign老板签字
public void sign(){
System.out.println("老板签字!");
}
}
然后创建一个SingletonPattern测试类
public class SingletonPattern {
public static void main(String[] args) {
//获取唯一可用对象
Boss boss = Boss.getInstance();
//老板签字
boss.sign();
}
}
得到结果
老板签字!
懒汉线程不安全
这种模式严格意义上不是单例模式,因为线程不安全。而单例模式要求是线程安全的。
单线程下没有问题,多线程下不能正常工作!
public class Singleton {
//定义唯一实例的引用
private static Singleton instance;
//私有化构造方法,防止外部创建实例
private Singleton(){
}
//判断引用是否指向实例,如果为空创建实例,如果存在则返回实例
public static Singleton getInstance(){
if(instance==null){
instance = new Singleton();
}
return instance;
}
}
懒汉线程安全
这个是真正意义上的单例,也是最常见的实现方式.
唯一的区别就是给获取实例的方法加了一把"锁",防止在刚启动的时候就有多个线程访问,造成创建多个实例的情况.
public class Singleton {
//定义唯一实例的引用
private static Singleton instance;
//私有化构造方法,防止外部创建实例
private Singleton(){
}
//判断引用是否指向实例,如果为空创建实例,如果存在则返回实例,同时锁住方法,防止重复创建
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
饿汉
饿汉与懒汉的区别在于:
饿汉是先创建实例,需要的时候直接返回创建好的实例;
懒汉是先不创建,当对象的getInstance方法被调用的时候再创建对象,可能会存在被同时调用的情况,所以需要加锁;
饿汉比较常用,缺点是容易产生垃圾对象;并且类加载的时候就创建对象,会造成内存的浪费.
public class Singleton {
//直接创建唯一实例,赋值给引用
private static Singleton instance = new Singleton();
//私有化构造方法,防止外部创建实例
private Singleton(){
}
//创建获取实例方法的时候记住一定要public(不然你给谁用)
public static Singleton getInstance(){
return instance;
}
}
DCL 双重校验锁
getInstance的性能决定了单例的性能.
DCL能够在多线程下保持高性能.
不同于懒汉式,DCL不是锁住整个方法,而是通过volatile保证变量不被编译器优化,同时只锁住对象,安全且高效;
缺点是实现难度相对较高.
//双重检验锁,兼具性能和安全,需要jdk1.5以上支持
public class Singleton {
//私有化引用,同时使用volatile来防止编译器优化,保证每次更新后所有线程都读取到最新值
private volatile static Singleton singleton;
//私有化构造方法,防止被外部创建
private Singleton(){
}
//获取实例的唯一方法,同时在创建的时候加一个对象锁
public static Singleton getInstance(){
/*说明下为什么有双重if
* 第一重if判断没有对象先锁住整个对象,防止其他线程读取
* 第二重if再次确认中间没有别的线程创建了对象
* 然后创建对象并返回
* */
if(singleton==null){
synchronized (Singleton.class){
if(singleton==null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
静态内部类/登记式
这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用
推荐使用这种方法,实现难度一般,但是效果和双检锁相同.
//登记式/静态内部类
public class Singleton {
//创建静态内部类,在类里面创建实例
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
//私有化构造方法,防止外部创建
private Singleton (){}
//直接返回内部类中的实例,这样不用加锁,并且静态区访问也快
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
枚举
这是剑走偏锋的方式,但是同时也是Effective Java 作者 Josh Bloch 提倡的方式,它实现起来更加简单,还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化
public enum Singleton {
//在这里定义实例
INSTANCE;
//下面可以写任意的方法
public void whateverMethod() {
}
}
总结
单例模式可以说是面试里问的最多的了,以上的方法不一定就是所有的方法,我们更关键的就是要知道单例的核心"怎么让应用中只存在一个实例",别的都是和多线程,性能相关的问题,这个可以后期不断研究和优化.