单例模式
单例模式8种创建方式
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全 同步方法)
- 懒汉式(线程安全 同步代码块)
- 双重检查(推荐使用)
- 静态内部类(推荐使用)
- 枚举(推荐使用)
1.饿汉式(静态常量)
创建步骤
- 构造器私有化(防止new)
- 类的内部创建对象
- 向外暴露一个静态的公共方法
- 代码实现
public class StaticVariableSingletonTest {
/**
* @param
* @return void
* @Description: 测试--饿汉式--静态变量
* @author luoyong
* @create 21:33 2019/9/7
* @last modify by [LuoYong 21:33 2019/9/7 ]
*/
@Test
public void test() {
StaticVariableSingleton singletonOne = StaticVariableSingleton.getInstance();
StaticVariableSingleton singletonOTwo = StaticVariableSingleton.getInstance();
//内存地址相同
System.out.println(singletonOne == singletonOTwo);
System.out.println("singletonOne.hashCode=" + singletonOne.hashCode());
System.out.println("singletonOTwo.hashCode=" + singletonOTwo.hashCode());
}
}
class StaticVariableSingleton {
/**
* 1.构造器私有化, 外部不能new
*/
private StaticVariableSingleton() {
}
/**
* 2.本类内部创建对象实例
*/
private final static StaticVariableSingleton instance = new StaticVariableSingleton();
/**
* @param
* @return
* @Description: 3 提供一个公有的静态方法,返回实例对象
* @author luoyong
* @create 21:33 2019/9/7
* @last modify by [LuoYong 21:33 2019/9/7 ]
*/
public static StaticVariableSingleton getInstance() {
return instance;
}
优点
写 法 比 较 简 单 , 在 类 装 载 的 时 候 完 成 实 例 化 , 避 免 了 线 程 同 步 的 问 题 \color{red}{写法比较简单,在类装载的时候完成实例化,避免了线程同步的问题} 写法比较简单,在类装载的时候完成实例化,避免了线程同步的问题
缺点
在 类 装 载 的 时 候 就 完 成 了 实 例 化 , 没 有 达 到 L a z y L o a d i n g 的 效 果 。 如 果 类 没 有 被 使 用 过 , 会 造 成 内 存 的 浪 费 \color{green}{在类装载的时候就完成了实例化,没有达到Lazy Loading的效果。如果类没有被使用过,会造成内存的浪费} 在类装载的时候就完成了实例化,没有达到LazyLoading的效果。如果类没有被使用过,会造成内存的浪费
结论
这 种 单 例 模 式 可 用 , 可 能 操 作 内 存 的 浪 费 。 ( 如 果 确 定 这 个 类 一 定 会 使 用 到 可 以 这 样 写 ) \color{blue}{这种单例模式可用,可能操作内存的浪费。(如果确定这个类一定会使用到 可以这样写)} 这种单例模式可用,可能操作内存的浪费。(如果确定这个类一定会使用到可以这样写)
2.饿汉式(静态代码块)
创建步骤
- 构造器私有化(防止new)
- 类的内部创建对象
- 向外暴露一个静态的公共方法
- 代码实现
public class StaticBlockSingletonTest {
/**
* @param
* @return void
* @Description: 饿汉式--静态代码块--测试
* @author luoyong
* @create 21:51 2019/9/7
* @last modify by [LuoYong 21:51 2019/9/7 ]
*/
@Test
public void test() {
Singleton singletonOne = Singleton.getInstance();
Singleton singletonOTwo = Singleton.getInstance();
//内存地址相同
System.out.println(singletonOne == singletonOTwo);
System.out.println("singletonOne.hashCode=" + singletonOne.hashCode());
System.out.println("singletonOTwo.hashCode=" + singletonOTwo.hashCode());
}
}
/**
* 饿汉式(静态变量)
*/
class Singleton {
/**
* 1.构造器私有化, 外部能new
*/
private Singleton() {
}
/**
* 2.本类内部创建对象实例
*/
private static Singleton instance;
static {
// 在静态代码块中,创建单例对象
instance = new Singleton();
}
/**
* @param
* @return
* @Description: 3.提供一个公有的静态方法,返回实例对象
* @author luoyong
* @create 21:51 2019/9/7
* @last modify by [LuoYong 21:51 2019/9/7 ]
*/
public static Singleton getInstance() {
return instance;
}
}
优点缺点–>同饿汉式静态常量
3.懒汉式(线程不安全)
实现
/**
* @author luoyong
* @Description: 懒汉式--线程不安全
* 起到了Lazy Loading的效果 但是只能在单线程下使用
* 在多线程的环境下可能产生多个实例
* 结论:在实际开发当中,不要使用这种方式
* @create 2019-09-07 22:00
* @last modify by [LuoYong 2019-09-07 22:00]
**/
public class ThreadUnsafeSingletonTest {
/**
* @param
* @return void
* @Description: 懒汉式--线程不安全--测试
* @author luoyong
* @create 22:02 2019/9/7
* @last modify by [LuoYong 22:02 2019/9/7 ]
*/
@Test
public void test() {
System.out.println("懒汉式--线程不安全");
Singleton singletonOne = Singleton.getInstance();
Singleton singletonOTwo = Singleton.getInstance();
//内存地址相同
System.out.println(singletonOne == singletonOTwo);
System.out.println("singletonOne.hashCode=" + singletonOne.hashCode());
System.out.println("singletonOTwo.hashCode=" + singletonOTwo.hashCode());
}
}
class Singleton {
/**
* 1.构造器私有化, 外部能new
*/
private Singleton() {
}
/**
* 2.本类内部创建对象实例
*/
private static Singleton instance;
/**
* @param
* @return
* @Description: 3.提供一个公有的静态方法,返回实例对象
* 调用的时候创建
* @author luoyong
* @create 21:51 2019/9/7
* @last modify by [LuoYong 21:51 2019/9/7 ]
*/
public static Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
return instance;
}
优点
起 到 了 L a z y − L o a d i n g 的 效 果 , 但 是 只 能 在 单 线 程 下 使 用 \color{red}{起到了Lazy-Loading的效果,但是只能在单线程下使用} 起到了Lazy−Loading的效果,但是只能在单线程下使用
缺点
在 多 线 程 下 , 一 个 线 程 进 入 了 i f ( n u l l = = i n s t a n c e ) 判 断 语 句 块 , 还 未 来 得 及 往 下 执 行 \color{green}{在多线程下,一个线程进入了if (null == instance)判断语句块,还未来得及往下执行} 在多线程下,一个线程进入了if(null==instance)判断语句块,还未来得及往下执行
另 一 个 线 程 也 进 入 了 这 个 判 断 语 句 块 , 这 时 便 会 产 生 多 个 实 例 \color{green}{另一个线程也进入了这个判断语句块,这时便会产生多个实例} 另一个线程也进入了这个判断语句块,这时便会产生多个实例
所 以 在 多 线 程 环 境 下 不 可 使 用 这 个 方 式 \color{green}{所以在多线程环境下不可使用这个方式} 所以在多线程环境下不可使用这个方式
结论
在 实 际 开 发 当 中 , 不 要 使 用 这 种 方 式 \color{blue}{在实际开发当中,不要使用这种方式} 在实际开发当中,不要使用这种方式
4-5.懒汉式–线程安全(同步代码块、同步方法)
实现
/**
* @author luoyong
* @Description: 懒汉式--线程安全
* 解决了线程安全的问题
* 缺点--效率太低,每个线程想获取该类的实例的时候 getInstance都要同步。
* 结论:在实际开发当中 不推荐使用这种方式
* @create 2019-09-07 22:08
* @last modify by [LuoYong 2019-09-07 22:08]
**/
public class ThreadSafeSingletonTest {
/**
* @param
* @return void
* @Description: 懒汉式--线程安全--加入同步处理代码--测试
* @author luoyong
* @create 22:02 2019/9/7
* @last modify by [LuoYong 22:02 2019/9/7 ]
*/
@Test
public void test() {
System.out.println("懒汉式--线程安全");
SingletonSafe singletonOne = SingletonSafe.getInstance();
SingletonSafe singletonOTwo = SingletonSafe.getInstance();
//内存地址相同
System.out.println(singletonOne == singletonOTwo);
System.out.println("singletonOne.hashCode=" + singletonOne.hashCode());
System.out.println("singletonOTwo.hashCode=" + singletonOTwo.hashCode());
}
}
class SingletonSafe {
/**
* 构造器私有化, 外部能new
*/
private SingletonSafe() {
}
/**
* 本类内部创建对象实例
*/
private static SingletonSafe instance;
/**
* @param
* @return
* @Description: 提供一个公有的静态方法,返回实例对象
* 调用的时候创建
* 加入同步处理的代码--解决线程安全问题
* @author luoyong
* @create 21:51 2019/9/7
* @last modify by [LuoYong 21:51 2019/9/7 ]
*/
public static synchronized SingletonSafe getInstance() {
if (null == instance) {
instance = new SingletonSafe();
}
return instance;
}
/**
* @param
* @return
* @Description: 这种方本意是对第四种方式进行改进,改成同步产生实例的化的代码块
* 缺点:这种方式不能保证线程同步。也可能产生多个实例
* 结论:在实际开发当中,不能使用这种方式
* @author luoyong
* @create 22:21 2019/9/7
* @last modify by [LuoYong 22:21 2019/9/7 ]
*/
public static SingletonSafe getInstanceUnSafe() {
if (null == instance) {
synchronized (SingletonSafe.class) {
instance = new SingletonSafe();
}
}
return instance;
}
同步方法优点
解 决 了 线 程 不 安 全 的 问 题 \color{red}{解决了线程不安全的问题} 解决了线程不安全的问题
同步方法缺点
效 率 太 低 , 每 个 线 程 在 想 获 取 类 的 实 例 的 时 候 , 执 行 g e t I n s t a n c e ( ) 方 法 都 要 进 行 同 步 \color{green}{效率太低,每个线程在想获取类的实例的时候,执行getInstance()方法都要进行同步} 效率太低,每个线程在想获取类的实例的时候,执行getInstance()方法都要进行同步
而 其 实 这 个 方 法 只 执 行 一 次 实 例 化 代 码 就 就 够 了 , 后 面 想 要 获 取 该 实 例 , 直 接 r e t u r n 就 可 以 了 , 方 法 进 行 同 步 效 率 太 低 \color{green}{而其实这个方法只执行一次实例化代码就就够了,后面想要获取该实例,直接return就可以了,方法进行同步效率太低} 而其实这个方法只执行一次实例化代码就就够了,后面想要获取该实例,直接return就可以了,方法进行同步效率太低
同步方法结论
在 实 际 开 发 中 , 不 推 荐 使 用 这 种 方 式 \color{blue}{在实际开发中,不推荐使用这种方式} 在实际开发中,不推荐使用这种方式
同步代码块优点
第 四 种 方 式 的 改 进 , 同 步 效 率 太 低 \color{red}{第四种方式的改进,同步效率太低} 第四种方式的改进,同步效率太低
同步代码块缺点
这 种 同 步 并 不 能 起 到 线 程 同 步 作 用 。 跟 第 三 种 实 现 方 式 遇 到 的 情 形 一 致 \color{green}{这种同步并不能起到线程同步作用。跟第三种实现方式遇到的情形一致} 这种同步并不能起到线程同步作用。跟第三种实现方式遇到的情形一致
假 如 一 个 线 程 进 入 了 i f ( s i n g l e t o n = = n u l l ) 判 断 语 句 块 , 还 未 来 得 及 往 下 执 行 , 另 一 个 线 程 也 通 过 了 这 个 判 断 语 句 , 这 时 便 会 产 生 多 个 实 例 \color{green}{假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行, 另一个线程也通过了这个判断语句,这时便会产生多个实例} 假如一个线程进入了if(singleton==null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例
同步代码块结论
在 实 际 开 发 中 , 不 能 使 用 这 种 方 式 \color{blue}{在实际开发中,不能使用这种方式} 在实际开发中,不能使用这种方式
6.双重检查
实现
/**
* @author luoyong
* @Description: 懒汉式--双重检查
* 在多线程开发当中经常使用到
* 优点:线程安全 延迟加载 效率较高
* 结论:在实际开发当中,推荐使用这种单例设计模式
* @create 2019-09-07 22:28
* @last modify by [LuoYong 2019-09-07 22:28]
**/
public class DoubleCheckSingletonTest {
/**
* @param
* @return void
* @Description: 测试双重检查实现单例
* @author luoyong
* @create 22:35 2019/9/7
* @last modify by [LuoYong 22:35 2019/9/7 ]
*/
@Test
public void test() {
System.out.println("双重检查--线程安全和懒加载");
Singleton singletonOne = Singleton.getInstance();
Singleton singletonOTwo = Singleton.getInstance();
//内存地址相同
System.out.println(singletonOne == singletonOTwo);
System.out.println("singletonOne.hashCode=" + singletonOne.hashCode());
System.out.println("singletonOTwo.hashCode=" + singletonOTwo.hashCode());
}
}
class Singleton {
private Singleton() {
}
/**
* 本类内部创建对象实例
* volatile
* 1:保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的(实现可见性)
* 2:禁止进行指令重排序。(实现有序性)
* 3:只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性
*/
private static volatile Singleton singleton;
/**
* @param
* @return
* @Description: 双重检查 既达到了线程安全,又实现了懒加载
* @author luoyong
* @create 22:21 2019/9/7
* @last modify by [LuoYong 22:21 2019/9/7 ]
*/
public static Singleton getInstance() {
if (null == singleton) {
synchronized (Singleton.class) {
if (null == singleton) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
优点
线 程 安 全 延 迟 加 载 效 率 较 高 \color{red}{线程安全 延迟加载 效率较高} 线程安全延迟加载效率较高
结论
在 实 际 开 发 过 程 中 , 推 荐 使 用 这 种 单 例 设 计 模 式 \color{blue}{在实际开发过程中,推荐使用这种单例设计模式} 在实际开发过程中,推荐使用这种单例设计模式
7.静态内部类
实现
/**
* @author luoyong
* @Description: 静态内部类--创建单例
* @create 2019-09-07 23:09
* @last modify by [LuoYong 2019-09-07 23:09]
**/
public class StaticInnerClassesSingletonTest {
/**
* @param
* @return void
* @Description: 静态内部类--创建单例--推荐
* @author luoyong
* @create 23:22 2019/9/7
* @last modify by [LuoYong 23:22 2019/9/7 ]
*/
@Test
public void test() {
System.out.println("静态内部类--线程安全和懒加载");
StaticInnerSingleton singletonOne = StaticInnerSingleton.getInstance();
StaticInnerSingleton singletonOTwo = StaticInnerSingleton.getInstance();
//内存地址相同
System.out.println(singletonOne == singletonOTwo);
System.out.println("singletonOne.hashCode=" + singletonOne.hashCode());
System.out.println("singletonOTwo.hashCode=" + singletonOTwo.hashCode());
}
}
class StaticInnerSingleton {
private StaticInnerSingleton() {
}
/**
* 静态内部类
* 1:StaticInnerSingleton 在装载的时候不会装载StaticInnerSingletonInit
* 2:jvm在装载类的时候是线程安全的
* 类的静态属性只会在第一次加载的时候初始化
* 推荐使用
*/
private static class StaticInnerSingletonInit {
private static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton();
}
public static StaticInnerSingleton getInstance() {
return StaticInnerSingletonInit.INSTANCE;
}
}
优缺点说明
1. 这 种 方 式 采 用 了 类 装 载 的 机 制 来 保 证 初 始 化 实 例 时 只 有 一 个 线 程 \color{blue}{1.这种方式采用了类装载的机制来保证初始化实例时只有一个线程} 1.这种方式采用了类装载的机制来保证初始化实例时只有一个线程
2. 静 态 内 部 类 方 式 在 S t a t i c I n n e r S i n g l e t o n 类 被 装 载 时 并 不 会 立 即 实 例 化 。 是 在 需 要 实 例 化 时 , 调 用 g e t I n s t a n c e 方 法 , 才 会 装 载 S t a t i c I n n e r S i n g l e t o n I n i t 类 , 从 而 完 成 S t a t i c I n n e r S i n g l e t o n 的 实 例 化 。 \color{blue}{2.静态内部类方式在StaticInnerSingleton类被装载时并不会立即实例化。是在需要实例化 时,调用getInstance方法,才会装载StaticInnerSingletonInit类,从而完成StaticInnerSingleton的 实例化。} 2.静态内部类方式在StaticInnerSingleton类被装载时并不会立即实例化。是在需要实例化时,调用getInstance方法,才会装载StaticInnerSingletonInit类,从而完成StaticInnerSingleton的实例化。
3. 类 的 静 态 属 性 只 会 在 第 一 次 加 载 类 的 时 候 初 始 化 , 所 以 在 这 里 , J V M 帮 助 我 们 保 证 了 线 程 的 安 全 性 , 在 类 进 行 初 始 化 时 , 别 的 线 程 是 无 法 进 入 的 。 \color{blue}{3.类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们 保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。} 3.类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
优 点 : 避 免 了 线 程 不 安 全 , 利 用 静 态 内 部 类 特 点 实 现 延 迟 加 载 , 效 率 高 \color{red}{优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高} 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
结 论 : 推 荐 使 用 \color{red}{结论:推荐使用} 结论:推荐使用
8.枚举
实现
/**
* @author luoyong
* @Description: 枚举--创建单例
* 借助JDK 1.5当中添加的枚举来实现单例模式
* 1:批量了多线程同步问题
* 2:防止反序列化重新创建新的对象
* 结论:推荐使用
* @create 2019-09-07 23:20
* @last modify by [LuoYong 2019-09-07 23:20]
**/
public class EnumSingletonTest {
@Test
public void test() {
System.out.println("枚举--线程安全和懒加载");
EnumSingleton singletonOne = EnumSingleton.INSTANCE;
EnumSingleton singletonOTwo = EnumSingleton.INSTANCE;
//内存地址相同
System.out.println(singletonOne == singletonOTwo);
System.out.println("singletonOne.hashCode=" + singletonOne.hashCode());
System.out.println("singletonOTwo.hashCode=" + singletonOTwo.hashCode());
}
}
enum EnumSingleton {
INSTANCE;
private void show() {
System.out.println("ok");
}
}
优缺点说明
1. 这 借 助 J D K 1.5 中 添 加 的 枚 举 来 实 现 单 例 模 式 。 不 仅 能 避 免 多 线 程 同 步 问 题 , 而 且 还 能 防 止 反 序 列 化 重 新 创 建 新 的 对 象 \color{blue}{1.这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而 且还能防止反序列化重新创建新的对象} 1.这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象
这 种 方 式 是 E f f e c t i v e J a v a 作 者 J o s h B l o c h 提 倡 的 方 式 \color{blue}{这种方式是Effective Java作者Josh Bloch 提倡的方式} 这种方式是EffectiveJava作者JoshBloch提倡的方式
结 论 : 推 荐 使 用 \color{red}{结论:推荐使用} 结论:推荐使用
源码使用举例
单例模式在JDK 应用的源码分析
单例模式注意事项和细节说明
注意事项
1. 单 例 模 式 保 证 了 系 统 内 存 中 该 类 只 存 在 一 个 对 象 , 节 省 了 系 统 资 源 , 对 于 一 些 需 要 频 繁 创 建 销 毁 的 对 象 , 使 用 单 例 模 式 可 以 提 高 系 统 性 能 \color{red}{1.单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需 要频繁创建销毁的对象,使用单例模式可以提高系统性能} 1.单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
2. 当 想 实 例 化 一 个 单 例 类 的 时 候 , 必 须 要 记 住 使 用 相 应 的 获 取 对 象 的 方 法 , 而 不 是 使 用 n e w \color{red}{2.当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使 用new} 2.当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new
使用场景
需 要 频 繁 的 进 行 创 建 和 销 毁 的 对 象 、 创 建 对 象 时 耗 时 过 多 或 耗 费 资 源 过 多 ( 即 : 重 量 级 对 象 ) , 但 又 经 常 用 到 的 对 象 、 工 具 类 对 象 、 频 繁 访 问 数 据 库 或 文 件 的 对 象 ( 比 如 数 据 源 、 s e s s i o n 工 厂 等 ) \color{blue}{需要频繁的进行创建和销毁的对象、创建对象时耗时过多或 耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数 据库或文件的对象(比如数据源、session工厂等)} 需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)