单例设计模式
什么是单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
-
1、单例类只能有一个实例。
-
2、单例类必须自己创建自己的唯一实例。
-
3、单例类必须给所有其他对象提供这一实例。
为什么使用单例设计模式
意图:
保证一个类仅有一个实例,并提供一个访问它的全局访问点对象
主要解决:
一个全局使用的类频繁地创建与销毁。
何时使用:
当您想控制实例数目,节省系统资源的时候。
单例设计模式8 种方法
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法)
- 懒汉式(线程安全,同步代码块)
- 双重检查
- 静态内部类
- 枚举
单例设计模式具体实现
1 )饿汉式(静态常量)实例
代码实现
package single;
public class SingletonTest {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
Singleton singleton1 = Singleton.getInstance();
System.out.println(singleton == singleton1);
System.out.println("singleton.hashCode: " + singleton.hashCode());
System.out.println("singleto1.hashCode: " + singleton1.hashCode());
}
}
//懒汉式(静态变量)
class Singleton {
// 创建一个私有的静态变量 并进行初始化
private final static Singleton singletonInstance = new Singleton();
// 构造器私有化 外部不能new
private Singleton() {
}
// 公开一个方法,获得唯一实例
public static Singleton getInstance() {
return singletonInstance;
}
}
/*
输出结果
true
singleton.hashCode: 366712642
singleto1.hashCode: 366712642
*/
优缺点与总结:
- 优点:这种方式写法简单,在类装载的时候就完成实例化,避免了线程同步问题
- 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading 懒加载 的效果,如果从始至终未使用过这个类,则会造成内存的浪费。(懒加载的意思就是什么时候用什么时候才加载)
- 结论:懒汉式单例设计模式可以用,但是会造成内存的浪费
2 )饿汉式(静态代码块)实例
代码实现
package single;
public class SingletonTest {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
Singleton singleton1 = Singleton.getInstance();
System.out.println(singleton == singleton1);
System.out.println("singleton.hashCode: " + singleton.hashCode());
System.out.println("singleto1.hashCode: " + singleton1.hashCode());
}
}
//懒汉式(静态变量)
class Singleton {
// 创建一个私有的静态变量
private static Singleton singletonInstance;
// 在静态代码块中 进行初始化
static{
singletonInstance = new Singleton();
}
// 构造器私有化 外部不能new
private Singleton() { }
public static Singleton getInstance() {
return singletonInstance;
}
}
/*
输出结果
true
singleton.hashCode: 366712642
singleto1.hashCode: 366712642
*/
优缺点与总结:
- 优缺点与上面一样
- 结论:懒汉式单例设计模式可以用,但是会造成内存的浪费
3 )懒汉式(线程不安全)实例
代码实现
package single;
public class SingletonTest02 {
public static void main(String[] args) {
System.out.println("懒汉式1 线程不安全");
Singleton singleton = Singleton.getInstance();
Singleton singleton1 = Singleton.getInstance();
System.out.println(singleton == singleton1);
System.out.println("singleton.hashCode: " + singleton.hashCode());
System.out.println("singleto1.hashCode: " + singleton1.hashCode());
}
}
//懒汉式(线程不安全)
class Singleton {
// 创建一个私有的静态变量
private static Singleton singletonInstance = null;
// 构造器私有化 外部不能new
private Singleton() { }
// 提供一个公共的静态方法,用到该方法时才实例化对象 即懒加载
public static Singleton getInstance() {
if(singletonInstance == null) {
singletonInstance = new Singleton();
}
return singletonInstance;
}
}
/*
输出结果:
懒汉式1 线程不安全
true
singleton.hashCode: 366712642
singleto1.hashCode: 366712642
*/
优缺点与总结:
-
优点:实现了Lazy Loading 懒加载 的效果
-
缺点:只能在单线程下使用,如果进入了多线程,
if(singletonInstance == null)
语句还未来得及向下执行,另一个线程也通过了这个判断语句,会导致产生多个实例,因此在严格意义上它并属于单例设计模式。在多线程下不能使用这种方式 -
结论:在实际开发中不要使用这种方式。
4 )懒汉式(线程安全 同步方法)实例
代码实现
package single;
public class SingletonTest01 {
public static void main(String[] args) {
System.out.println("懒汉式2 线程安全 同步方法");
Singleton singleton = Singleton.getInstance();
Singleton singleton1 = Singleton.getInstance();
System.out.println(singleton == singleton1);
System.out.println("singleton.hashCode: " + singleton.hashCode());
System.out.println("singleto1.hashCode: " + singleton1.hashCode());
}
}
//懒汉式(线程安全 同步方法)
class Singleton {
// 创建一个私有的静态变量 并进行初始化
private static Singleton singletonInstance = null;
// 构造器私有化 外部不能new
private Singleton() { }
//利用synchronized修饰getInstance方法,使之变为同步方法 懒加载
public synchronized static Singleton getInstance() {
if(singletonInstance == null) {
singletonInstance = new Singleton();
}
return singletonInstance;
}
}
/*
输出结果:
懒汉式2 线程安全 同步方法
true
singleton.hashCode: 366712642
singleto1.hashCode: 366712642
*/
优缺点与总结:
- 优点:解决了线程安全的问题
- 缺点:效率太低,每个线程想要执行getInstance()方法时,都要进行同步,实际上这个方法只执行一次实例化就够了,之后就直接return 实例就好了,方法进行同步效率太低
- 结论:在实际开发中不推荐这种方式。
5 )懒汉式(线程不安全 同步代码块)实例
有的人也许会认为把上面同步方法换成同步代码块就可以了:如下:
package single;
public class SingletonTest {
public static void main(String[] args) {
System.out.println("懒汉式3 线程不安全 同步代码块");
Singleton singleton = Singleton.getInstance();
Singleton singleton1 = Singleton.getInstance();
System.out.println(singleton == singleton1);
System.out.println("singleton.hashCode: " + singleton.hashCode());
System.out.println("singleto1.hashCode: " + singleton1.hashCode());
}
}
//懒汉式(线程安全 同步代码块)
class Singleton {
// 创建一个私有的静态变量
private static Singleton singletonInstance = null;
// 构造器私有化 外部不能new
private Singleton() { }
// 提供一个公共的静态方法,用到该方法时才实例化对象 即懒加载
public static Singleton getInstance() {
if(singletonInstance == null) {
// 在这里设置静态代码块 并不能解决同步问题 所以这种方法不可以
synchronized (Singleton.class) {
singletonInstance = new Singleton();
}
}
return singletonInstance;
}
}
/*
输出结果:
懒汉式3 线程不安全 同步代码块
true
singleton.hashCode: 366712642
singleto1.hashCode: 366712642
*/
优缺点与总结:
-
优缺点与 3) 一样
-
结论:在实际开发中不要使用这种方式。
6)双重检查 实例
代码实现
package single;
public class SingletonTest {
public static void main(String[] args) {
System.out.println("双重检查 线程安全");
Singleton singleton = Singleton.getInstance();
Singleton singleton1 = Singleton.getInstance();
System.out.println(singleton == singleton1);
System.out.println("singleton.hashCode: " + singleton.hashCode());
System.out.println("singleto1.hashCode: " + singleton1.hashCode());
}
}
// 双重检查
class Singleton {
/**
1.volatile
volatile 是一个类型修饰符。volatile 的作用是作为指令关键字,确保本条指令不会因编译器的 优化而省略。
2.volatile 的特性
保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程 来说是立即可见的,(即实时刷新)。(实现可见性) 禁止进行指令重排序。(实现有序性)
volatile 只能保证对单次读 / 写的原子性。i++ 这种操作不能保证原子性。
*/
// 创建一个私有的静态变量 并用volatile修饰
private static volatile Singleton singletonInstance = null;
// 构造器私有化 外部不能new
private Singleton() { }
// 提供一个公共的静态方法,用到该方法时才实例化对象 加入双重检查的代码 解决线程安全 以及懒加载
public static Singleton getInstance() {
// 第一次判断 如果实例化直接返回 否则进行实例化
if(singletonInstance == null) {
// 设置静态代码块 保证每次只能进入一个线程
synchronized (Singleton.class) {
// 第二次判断 保证只能实例化一次
if(singletonInstance == null) {
singletonInstance = new Singleton();
}
}
}
return singletonInstance;
}
}
/*
输出结果:
双重检查 线程安全
true
singleton.hashCode: 366712642
singleto1.hashCode: 366712642
*/
优缺点与总结:
- 优点:双重检查(double-check) 概念时多线程开发中经常用到的,在代码中我们进行了两次
if(singletonInstance == null)
判断,解决了线程安全问题 - 第一个线程调用完getInstance方法后,其他的线程在第一层判断时,就会直接返回实例,避免反复进入同步代码块,解决了效率问题
- 结论:在实际开发中推荐使用这种方式。
7)静态内部类 实例
代码实现
package single;
public class SingletonTest {
public static void main(String[] args) {
System.out.println("静态内部类 线程安全 懒加载");
Singleton singleton = Singleton.getInstance();
Singleton singleton1 = Singleton.getInstance();
System.out.println(singleton == singleton1);
System.out.println("singleton.hashCode: " + singleton.hashCode());
System.out.println("singleto1.hashCode: " + singleton1.hashCode());
}
}
// 静态内部类
class Singleton {
// 原因:
/*
在Singleton进行装载的时候 其静态内部类不进行装载,等到getInstance方法返回静态内部类中
常量时静态内部类才进行装载,其常量才进行实例化,且装载过程为线程安全,并实现了懒加载。
*/
//创建静态内部类,利用其特性 实现线程安全,以及懒加载
private static class SingletonInstance{
private static final Singleton INSTANCE = new Singleton();
}
// 构造器私有化 外部不能new
private Singleton() { }
// 提供一个公共的静态方法 获得实例
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
/*
静态内部类 线程安全 懒加载
true
singleton.hashCode: 366712642
singleto1.hashCode: 366712642
*/
优缺点与总结:
- 在Singleton进行装载的时候 其静态内部类SingletonInstance并不进行装载,等到需要实例化时调用getInstance方法时,静态内部类SingletonInstance才进行装载,完成Singleton类的实例化,类的静态属性,只有在第一次装载类的时候进行初始化,JVM帮我们实现了线程的安全性,并实现了懒加载。
- 优点: 避免了线程的不安全,利用静态类实现延迟加载,效率高。
- 结论:在实际开发中推荐使用这种方式。
8 )枚举 实例
代码展示:
package single;
public class SingletonTest {
public static void main(String[] args) {
System.out.println("JDK1.5 枚举 线程安全");
Singleton singleton = Singleton.INSTANCE;
Singleton singleton1 = Singleton.INSTANCE;
System.out.println(singleton == singleton1);
System.out.println("singleton.hashCode: " + singleton.hashCode());
System.out.println("singleto1.hashCode: " + singleton1.hashCode());
}
}
// 静态内部类,实现单例
enum Singleton {
INSTANCE;
public void hello() {
System.out.println("hello");
}
}
/*
JDK1.5 枚举 线程安全
true
singleton.hashCode: 366712642
singleto1.hashCode: 366712642
*/
优缺点与总结:
- 这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。 - 结论:推荐使用
本人公众号