参照B站尚硅谷官方视频资源:https://www.bilibili.com/video/av57936239?p=33
单例模式: 确保此类只有一个实例,并提供获取实例的方法。
作用: 可以保持在一个应用程序生命周期内,所引用对象的实例均为同一个
使用场景:例如工具类,使用了单例模式后,可避免重复创建实例造成的资源浪费。
单例模式分为以下7种:
也可说是5种,只是懒汉饿汉各自又有2种不同的实现方式。
此外还介绍一种可能在面试中会提到的错误写法:懒汉式(同步代码块),实际是一种线程不安全的写法。
懒汉:类装载的时候不实例化,使用时才实例化, 即延迟加载,需要考虑线程安全
饿汉:类装载的时候就实例化(管你之后用不用,先实例化了),本身就线程安全
通过以下三个步骤实现
具体编码如下:
/**
* 1.饿汉式(静态常量)实现单例模式
* 步骤:
* 1. 私有化构造函数
* 2. 类的内部创建对象
* 3. 对外暴露一个获取实例的静态方法
*/
public class EagerSingleton1 {
// 1.私有化构造函数
private EagerSingleton1(){
//初始化工作...
}
// 2.本类内部创建对象的实例
private final static EagerSingleton1 instance=new EagerSingleton1();
//3.对外提供一个获取实例的静态方法
public static EagerSingleton1 getInstance() {
return instance;
}
}
测试代码如下:
//单例模式测试类
public class SingleTest {
public static void main(String[] args) {
//测试 1.饿汉式(静态常量)实现单例模式
EagerSingleton1 eagerSingleton11=EagerSingleton1.getInstance();
EagerSingleton1 eagerSingleton12=EagerSingleton1.getInstance();
//判断这两实例 是否为同一个
System.out.println(eagerSingleton11==eagerSingleton12);
}
}
测试结果:
true
使用饿汉式(静态常量)实现单例模式:
优点:写法简单;使用getInstance()方法类装载时实例化,所以线程安全。
缺点:没有懒加载,所以若一直没使用该实例则导致了内存浪费
和使用静态常量的饿汉式单例模式大致相同:
/**
* 饿汉式(静态代码块)实现单例模式
*/
public class EagerSingleton2 {
// 1.私有化构造函数
private EagerSingleton2(){
//初始化工作...
}
// 2.静态代码块中创建实例
private static EagerSingleton2 instance;
static {
instance=new EagerSingleton2();
}
//3.对外提供一个获取实例的静态方法
public static EagerSingleton2 getInstance() {
return instance;
}
}
//单例模式测试类
public class SingleTest {
public static void main(String[] args) {
//测试 2.饿汉式(静态代码块)实现单例模式
EagerSingleton2 eagerSingleton21=EagerSingleton2.getInstance();
EagerSingleton2 eagerSingleton22=EagerSingleton2.getInstance();
//判断这两实例 是否为同一个
System.out.println(eagerSingleton21==eagerSingleton22);
}
}
测试结果:
true
与第一种使用静态常量的饿汉单例模式类似,只是实例化对象的过程放在了静态代码块中,也是类装载时实例化对象。
/**
* 3.懒汉式(线程不安全)单例模式 (1)
*/
public class LazySingleton1 {
private static LazySingleton1 lazySingleton1;
//1.私有化构造函数
private LazySingleton1() {
}
//对外暴露获取实例的方法
//外面需要使用该实例时(即调用getInstance()),才创建实例
public static LazySingleton1 getInstance() {
if (lazySingleton1 == null) {
//没有才创建
lazySingleton1 = new LazySingleton1();
}
return lazySingleton1;
}
}
//单例模式测试类
public class SingleTest {
public static void main(String[] args) {
//测试 3.懒汉式(线程不安全)单例模式 (1)
LazySingleton1 lazySingleton11 = LazySingleton1.getInstance();
LazySingleton1 lazySingleton12 = LazySingleton1.getInstance();
//判断这两实例 是否为同一个
System.out.println(lazySingleton11 == lazySingleton12);
}
}
测试结果:
true
优点:实现了实例懒加载
缺点:单线程可以使用,但在多线程中,可能存在这种情况:一个线程执行完 if (lazySingleton1 == null)后还没来得及执行条件成立的代码,另一个线程也执行了 if (lazySingleton1 == null),这样多个线程进入会导致创建多个实例,没有达到单例的效果。
即:多线程不可使用
和第三种唯一的区别就是在获取实例的方法(getInstance)加入了synchronized关键字,使其为同步方法。
/**
* 4.懒汉式(线程安全,同步方法)实现单例模式(1)
*/
public class LazySingleton2 {
private static LazySingleton2 lazySingleton2;
//1.私有化构造函数
private LazySingleton2() {
}
//对外暴露获取实例的方法
//外面需要使用该实例时(即调用getInstance()),才创建实例
public static synchronized LazySingleton2 getInstance() {
if (lazySingleton2 == null) {
//没有才创建
lazySingleton2 = new LazySingleton2();
}
return lazySingleton2;
}
}
//单例模式测试类
public class SingleTest {
public static void main(String[] args) {
//测试 4.懒汉式(线程安全,同步方法)单例模式 (2)
LazySingleton2 lazySingleton21 = LazySingleton2.getInstance();
LazySingleton2 lazySingleton22 = LazySingleton2.getInstance();
//判断这两实例 是否为同一个
System.out.println(lazySingleton21 == lazySingleton22);
}
}
优点:实现了对象实例化的懒加载;线程安全。
缺点:效率低,因为实例的初始化只需一次,但是每次调用getInstance都需要同步。(就是同步方法真正有正面作用的执行次数只有一次,而之后的执行由于需要同步都拖累了系统运行)
实际开发不推荐使用
这是错误的写法!错误的写法!错误的写法!
面试可能会有这样的问题,所以还是记录了一下。
这种写法的由来是想改进第四种:懒汉式(线程安全,同步方法) 效率低的问题,但是在多线程下,依然会有多个线程都进入if判断成立的代码块,所以依然会有生成多个实例的风险。所以带来了线程不安全的问题。
/**
* 5.懒汉式(线程不安全,同步代码块)单例模式 (3)
*
* 错误写法!了解缺陷即可
* 错误写法!了解缺陷即可
* 错误写法!了解缺陷即可
*/
public class LazySingleton3 {
private static LazySingleton3 lazySingleton3;
//1.私有化构造函数
private LazySingleton3() {
}
//对外暴露获取实例的方法
//外面需要使用该实例时(即调用getInstance()),才创建实例
public static LazySingleton3 getInstance() {
if (lazySingleton3 == null) {
//没有才创建
synchronized (LazySingleton3.class){
lazySingleton3 = new LazySingleton3();
}
}
return lazySingleton3;
}
}
由于本身是错误的写法,就不测了。
这种写法线程不安全,不能起到线程同步的作用!!
由于可能多个线程进入if成立的代码段,所以那个同步代码块根本不起作用,不能使线程同步。
这种写法实际开发不能用!,但面试可能会问,还是了解一下。
本文为看视频后的笔记摘要:
B站尚硅谷官方视频资源《尚硅谷图解Java设计模式韩顺平老师2019力作》:https://www.bilibili.com/video/av57936239?p=33
会在下一篇笔记中提及剩下三种单例模式的写法:
双重检查单例模式,静态内部类单例模式,枚举单例模式
Java设计模式-单例模式的7种写法详解(下)