单例模式(两种)
Posted ohana!
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式(两种)相关的知识,希望对你有一定的参考价值。
目录
一,概念
- 单例模式是指在内存中只会创建且仅创建一次对象的设计模式
- 在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象
二,应用实例
在jdbc操作时,使用了DataSource(数据库连接池)
三,饿汉模式
- 饿汉式:在类加载时已经创建好该单例对象,等待被程序使用
public class Singleton
//在类加载时就已经创建好,不存在线程安全和并发问题
private static Singleton instance = new Singleton();
private static Singleton getInstance()
return instance;
四,懒汉模式(单线程)
- 懒汉式:在真正需要使用对象时才去创建该单例类对象
public class SingletonLazy
//类加载时不创建,在调用方法时才实例化一个单例对象
//线程不安全
private static SingletonLazy instance = null;
public static SingletonLazy getInstance()
if(instance == null)
instance = new SingletonLazy();
return instance;
五,懒汉模式(多线程且效率低)
public class SingletonLazy
//类加载时不创建,在调用方法时才实例化一个单例对象
private static SingletonLazy instance = null;
public static SingletonLazy getInstance()
//线程安全了,但是每次获取对象都需要先获取锁,并发性能非常地差
//极端情况下,可能会出现卡顿现象
synchronized (SingletonLazy.class)
if (instance == null)
instance = new SingletonLazy();
return instance;
六,懒汉模式(双重校验锁)
public class SingletonLazy
//类加载时不创建,在调用方法时才实例化一个单例对象
private static volatile SingletonLazy instance = null;
public static SingletonLazy getInstance()
//线程安全了,但是每次获取对象都需要先获取锁,并发性能非常地差
//极端情况下,可能会出现卡顿现象
if(instance == null)
synchronized (SingletonLazy.class)
if (instance == null)
instance = new SingletonLazy();
return instance;
关于volatile锁的作用:防止指令重排序
创建一个对象,在JVM中会经过三步:
(1)为instance分配内存空间
(2)初始化instance对象
(3)将instance指向分配好的内存空间
指令重排序是指:
- JVM在保证最终结果正确的情况下,可以不按照程序编码的顺序执行语句,尽可能提高程序的性能
- 使用volatile关键字修饰的变量,可以保证其指令执行的顺序与程序指明的顺序一致,不会发生顺序变换
- 使用volatile关键字修饰的变量,可以保证其内存可见性,即每一时刻线程读取到该变量的值都是内存中最新的那个值,线程每次操作该变量都需要先读取该变量。
七,破坏单例模式
无论是完美的懒汉式还是饿汉式,终究敌不过反射和序列化,它们俩都可以把单例对象破坏掉(产生多个对象)。
1:利用反射破坏单例模式(利用反射,强制访问类的私有构造器,去创建另一个对象 )
public static void main(String[] args)
// 获取类的显式构造器
Constructor<Singleton> construct = Singleton.class.getDeclaredConstructor();
// 可访问私有构造器
construct.setAccessible(true);
// 利用反射构造新对象
Singleton obj1 = construct.newInstance();
// 通过正常方式获取单例对象
Singleton obj2 = Singleton.getInstance();
System.out.println(obj1 == obj2); // false
2:利用序列化与反序列化破坏单例模式
public static void main(String[] args)
// 创建输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Singleton.file"));
// 将单例对象写到文件中
oos.writeObject(Singleton.getInstance());
// 从文件中读取单例对象
File file = new File("Singleton.file");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Singleton newInstance = (Singleton) ois.readObject();
// 判断是否是同一个对象
System.out.println(newInstance == Singleton.getInstance()); // false
在 JDK1.5 后,使用 Java 语言实现单例模式的方式又多了一种:枚举
public enum Singleton
INSTANCE;
public void doSomething()
System.out.println("这是枚举类型的单例模式!");
优势1:代码对比饿汉式与懒汉式来说,更加地简洁
优势2:它不需要做任何额外的操作去保证对象单一性与线程安全性
优势3:使用枚举可以防止调用者使用反射、序列化与反序列化机制强制生成多个单例对象,破坏单例模式
八,总结
(1)单例模式常见的写法有两种:懒汉式、饿汉式
(2)懒汉式:在需要用到对象时才实例化对象,正确的实现方式是:双重校验锁,解决了并发安全和性能低下问题
(3)饿汉式:在类加载时已经创建好该单例对象,在获取单例对象时直接返回对象即可,不会存在并发安全和性能问题。
(4)在开发中如果对内存要求非常高,那么使用懒汉式写法,可以在特定时候才创建该对象;
(5)如果对内存要求不高使用饿汉式写法,因为简单不易出错,且没有任何并发安全和性能问题
(6)为了防止多线程环境下,因为指令重排序导致变量报异常,需要在单例对象上添加volatile关键字防止指令重排序
(7)最优雅的实现方式是使用枚举,其代码精简,没有线程安全问题,且 Enum 类内部防止反射和反序列化时破坏单例。
以上是关于单例模式(两种)的主要内容,如果未能解决你的问题,请参考以下文章