设计模式之单例模式
1 介绍
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
2 实现步骤
所有的单例实现都包含以下两个相同的步骤:
- 将默认构造方法设为私有,防止其他对象使用new直接创建对象。
- 新建一个静态构建方法作为构造函数。函数调用私有构造方法来创建对象,并将其保存在一个静态成员变量中。之后所有对于该函数的调用将返回这一缓存对象。
UML类图实现如下:
3 八种实现方式
3.1 饿汉式(静态常量)
/**
* 是否 Lazy 初始化:否
* 是否多线程安全:是
* 实现难度:易
* JDK中的Runtime就是使用的这种模式
*/
public class Singleton1 {
// 2 本类内部创建对象实例
private static final Singleton1 instance = new Singleton1();
// 1 构造器私有化,外部不能new
private Singleton1(){}
// 3 提供一个公有的静态方法,返回实例对象
public static Singleton1 getInstance(){
return instance;
}
}
3.2 饿汉式(静态代码块)
/**
* 是否 Lazy 初始化:否
* 是否多线程安全:是
* 实现难度:易
*/
public class Singleton2 {
// 2 本类内部创建对象实例
private static Singleton2 instance;
static {
instance = new Singleton2();
}
// 1 构造器私有化,外部不能new
private Singleton2(){}
// 3 提供一个公有的静态方法,返回实例对象
public static Singleton2 getInstance(){
return instance;
}
}
饿汉式单例优缺点如下:
- 优点
- 单例对象的创建是线程安全的。
- 获取单例对象的方法不需要加锁。
- 代码实现简单。
- 缺点
- 单例对象的创建不是懒加载机制。
不管程序是否需要这个对象的实例,总是在类加载的时候就先创建好实例,就像不管一个人想不想吃东西,都要把吃的先买好,如同饿怕了的恶汉一样,因此叫饿汉式。
3.3 懒汉式(线程不安全)
/**
* 是否 Lazy 初始化:是
* 是否多线程安全:否
* 实现难度:易
*/
public class Singleton3 {
private static Singleton3 instance;
private Singleton3(){}
public static Singleton3 getInstance(){
if (instance==null){
instance = new Singleton3();
}
return instance;
}
}
3.4 懒汉式(线程安全,同步方法)
/**
* 是否 Lazy 初始化:是
* 是否多线程安全:是
* 实现难度:易
*/
public class Singleton4 {
private static Singleton4 instance;
private Singleton4(){}
public static synchronized Singleton4 getInstance(){
if (instance==null){
instance = new Singleton4();
}
return instance;
}
}
3.5 懒汉式(线程安全,同步代码块)
/**
* 是否 Lazy 初始化:是
* 是否多线程安全:是
* 实现难度:易
*/
public class Singleton5 {
private static Singleton5 instance;
private Singleton5(){}
public static Singleton5 getInstance(){
if (instance==null){
synchronized (Singleton5.class) {
instance = new Singleton5();
}
}
return instance;
}
}
懒汉式单例优缺点如下:
- 优点
- 对象的创建是线程安全的。
- 支持懒加载机制。
- 缺点
- 获取对象的方法上了锁,影响并发度。
当程序需要这个实例的时候才去创建对象,就如同一个人懒到饿的不行才去吃东西,因此叫懒汉式。
3.6 双检锁/双重校验锁
/**
* JDK 版本:JDK1.5 起
* 是否 Lazy 初始化:是
* 是否多线程安全:是
* 实现难度:较复杂
*/
public class Singleton6 {
private static volatile Singleton6 instance;
private Singleton6(){}
public static Singleton6 getInstance(){
if(instance==null){
synchronized (Singleton6.class){
if(instance==null){
instance = new Singleton6();
}
}
}
return instance;
}
}
双重校验锁单例优缺点如下:
- 优点
- 对象的创建是线程安全的。
- 支持懒加载机制。
- 获取对象的方法不需要加锁。
- 缺点
- 实现复杂。
3.7 静态内部类
/**
* 是否 Lazy 初始化:是
* 是否多线程安全:是
* 实现难度:一般
*/
public class Singleton7 {
private Singleton7(){}
private static class SingletonHolder{
private static final Singleton7 INSTANCE = new Singleton7();
}
public static Singleton7 getInstance(){
return SingletonHolder.INSTANCE;
}
}
静态内部类单例优点如下:
- 优点
- 对象的创建时线程安全的。
- 支持懒加载机制。
- 获取对象的方法不需要加锁。
3.8 枚举
/**
* JDK 版本:JDK1.5 起
* 是否 Lazy 初始化:否
* 是否多线程安全:是
* 实现难度:易
*/
public enum Singleton8 {
INSTANCE;
public void whateverMethod() {
System.out.println(Thread.currentThread().getName()+":"+this);
}
}
4 在JDK中的使用
在JDK中有Runtime类,这个类就使用了饿汉式静态常量的实现方式,核心代码如下:
// JDK中的RunTime类
public class Runtime {
// 定义了一个静态的变量
private static Runtime currentRuntime = new Runtime();
// 一个私有的构造器
private Runtime() {}
// 获取唯一实例的静态方法
public static Runtime getRuntime() {
return currentRuntime;
}
}
5 总结
对于单例模式核心概念有三点:单例模式是一种创建型的设计模式。在某个范围内,一个类只有一个实例存在。某个范围内,只提供一个访问实例的全局节点,这个范围可以是进程、线程、集群和类加载器。对于其实现方式,每个方式都有自己的优缺点,大家在使用的时候,可以根据具体的实际场景来选择,建议大家记住双重校验锁和静态内部类的实现方式。
【温馨提示】
点赞+收藏文章,关注我并私信回复【面试题解析】,即可100%免费领取楼主的所有面试题资料!